Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(scroll-on-touch): use finger to scroll on canvas #104

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.DS_Store
node_modules
.cache
.idea
dist
# Cypress
cypress/screenshots/
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const Canvas = class extends React.Component {
## List of Props

| Props | Expected datatype | Default value | Description |
| ---------------------------------- | ----------------- | --------------------- | --------------------------------------------------------------------------------------------------- |
|------------------------------------|-------------------| --------------------- |-----------------------------------------------------------------------------------------------------|
| width | PropTypes.string | 100% | canvas width (em/rem/px) |
| height | PropTypes.string | 100% | canvas width (em/rem/px) |
| id | PropTypes.string | "react-sketch-canvas" | ID field to uniquely identify a SVG canvas (Supports multiple canvases in a single page) |
Expand All @@ -136,6 +136,7 @@ const Canvas = class extends React.Component {
| allowOnlyPointerType | PropTypes.string | all | allow pointer type ("all"/"mouse"/"pen"/"touch") |
| onChange | PropTypes.func | | Returns the current sketch path in `CanvasPath` type on every path change |
| onStroke | PropTypes.func | | Returns the the last stroke path and whether it is an eraser stroke on every pointer up event |
| scrollOnTouch | PropTypes.bool | false | Scroll overflowing component instead of sketching if pointerType === touch |
| style | PropTypes.object | false | Add CSS styling as CSS-in-JS object |
| svgStyle | PropTypes.object | {} | Add CSS styling as CSS-in-JS object for the SVG |
| withTimestamp | PropTypes.bool | false | Add timestamp to individual strokes for measuring sketching time |
Expand Down
54 changes: 46 additions & 8 deletions packages/react-sketch-canvas/src/Canvas/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react/no-array-index-key */
import * as React from "react";
import { useCallback } from "react";
import { useCallback, useEffect } from "react";
import Paths, { SvgPath } from "../Paths";
import { CanvasPath, ExportImageType, Point } from "../types";

Expand Down Expand Up @@ -35,6 +35,7 @@ export interface CanvasProps {
paths: CanvasPath[];
isDrawing: boolean;
onPointerDown: (point: Point, isEraser?: boolean) => void;
scrollOnTouch: boolean;
onPointerMove: (point: Point) => void;
onPointerUp: () => void;
className?: string;
Expand All @@ -59,6 +60,7 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
const {
paths,
isDrawing,
scrollOnTouch,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users must not be able to set allowOnlyPointerType as touch or all while they set scrollOnTouch. ReactSketchCanvas must throw an error with some meaningful error message to avert users from running into this scenario. CanvasProps must also be updated to be conditional here.

onPointerDown,
onPointerMove,
onPointerUp,
Expand All @@ -79,6 +81,7 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
} = props;

const canvasRef = React.useRef<HTMLDivElement>(null);
const currentPointerType = React.useRef<"pen" | "mouse" | "touch" | null>(null);

// Converts mouse coordinates to relative coordinate based on the absolute position of svg
const getCoordinates = useCallback(
Expand All @@ -104,10 +107,20 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {

const handlePointerDown = useCallback(
(event: React.PointerEvent<HTMLDivElement>): void => {
// Allow only chosen pointer type
currentPointerType.current = event.pointerType;

if (
allowOnlyPointerType !== "all" &&
if (scrollOnTouch && event.pointerType === "touch") {
return;
}

if (event.pointerType === 'pen') {
event.preventDefault();
event.stopPropagation();
}

// Allow only chosen pointer type
if (
allowOnlyPointerType !== "all" &&
event.pointerType !== allowOnlyPointerType
) {
return;
Expand All @@ -129,9 +142,13 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
(event: React.PointerEvent<HTMLDivElement>): void => {
if (!isDrawing) return;

// Allow only chosen pointer type
if (
allowOnlyPointerType !== "all" &&
if (scrollOnTouch && event.pointerType === "touch") {
return;
}

// Allow only chosen pointer type
if (
allowOnlyPointerType !== "all" &&
event.pointerType !== allowOnlyPointerType
) {
return;
Expand All @@ -147,6 +164,9 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
const handlePointerUp = useCallback(
(event: React.PointerEvent<HTMLDivElement> | PointerEvent): void => {
if (event.pointerType === "mouse" && event.button !== 0) return;
if (scrollOnTouch && event.pointerType === "touch") {
return;
}

// Allow only chosen pointer type
if (
Expand Down Expand Up @@ -291,13 +311,31 @@ release drawing even when point goes out of canvas */
);
}, [paths]);

// avoid pen from scrolling if scrollOnTouch
useEffect(() => {
const listener = function(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to move this listener as a callback using useCallback with empty dependency array.

this: HTMLDivElement,
event: TouchEvent
): void {
if (currentPointerType.current === "pen") {
event.preventDefault();
event.stopPropagation();
}
};

if (scrollOnTouch) {
canvasRef.current?.addEventListener("touchstart", listener, { passive: false });
}
return () => canvasRef.current?.removeEventListener("touchstart", listener);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canvasRef.current might get cleaned up before this cleanup is run. so canvasRef.current must be copied to a variable in the scope of this function. and then you can add or remove event listeners.

}, []);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the dependency array is empty, if the developer changes scrollOnTouch dynamically, this function will fail.


return (
<div
role="presentation"
ref={canvasRef}
className={className}
style={{
touchAction: "none",
touchAction: scrollOnTouch ? 'pan-y' : 'none',
width,
height,
...style,
Expand Down
3 changes: 3 additions & 0 deletions packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface ReactSketchCanvasProps {
svgStyle?: React.CSSProperties;
width?: string;
withTimestamp?: boolean;
scrollOnTouch?: boolean;
}

export interface ReactSketchCanvasRef {
Expand Down Expand Up @@ -61,6 +62,7 @@ export const ReactSketchCanvas = React.forwardRef<
onChange = (_paths: CanvasPath[]): void => undefined,
onStroke = (_path: CanvasPath, _isEraser: boolean): void => undefined,
withTimestamp = false,
scrollOnTouch = false,
} = props;

const svgCanvas = React.createRef<CanvasRef>();
Expand Down Expand Up @@ -262,6 +264,7 @@ export const ReactSketchCanvas = React.forwardRef<
svgStyle={svgStyle}
paths={currentPaths}
isDrawing={isDrawing}
scrollOnTouch={scrollOnTouch}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
Expand Down