Skip to content

Commit fc3d43f

Browse files
committed
fix(Canvas): Improved behavior when using apple pencil
Fixed bug: Writing on the drawing board with the iPad pen will lose focus and a selection box will pop up Close vinothpandian#171
1 parent bcbb141 commit fc3d43f

File tree

2 files changed

+88
-35
lines changed

2 files changed

+88
-35
lines changed

packages/react-sketch-canvas/src/Canvas/index.tsx

+84-35
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ import {
77
ExportImageOptions,
88
ExportImageType,
99
Point,
10+
TouchExtends,
1011
} from "../types";
1112
import { CanvasProps, CanvasRef } from "./types";
1213

14+
const TOUCH_TYPE_MAP: Record<string, string> = {
15+
'direct': 'touch',
16+
'stylus': 'pen',
17+
default: 'mouse'
18+
}
19+
1320
const loadImage = (url: string): Promise<HTMLImageElement> =>
1421
new Promise((resolve, reject) => {
1522
const img = new Image();
@@ -79,7 +86,7 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
7986

8087
// Converts mouse coordinates to relative coordinate based on the absolute position of svg
8188
const getCoordinates = useCallback(
82-
(pointerEvent: React.PointerEvent<HTMLDivElement>): Point => {
89+
(pointerEvent: TouchEvent | MouseEvent): Point => {
8390
const boundingArea = canvasRef.current?.getBoundingClientRect();
8491
canvasSizeRef.current = boundingArea
8592
? {
@@ -94,10 +101,20 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
94101
if (!boundingArea) {
95102
return { x: 0, y: 0 };
96103
}
104+
let x = 0;
105+
let y = 0;
106+
107+
if (pointerEvent instanceof TouchEvent) {
108+
x = pointerEvent.touches[0].pageX;
109+
y = pointerEvent.touches[0].pageY;
110+
} else {
111+
x = pointerEvent.pageX;
112+
y = pointerEvent.pageY;
113+
}
97114

98115
return {
99-
x: pointerEvent.pageX - boundingArea.left - scrollLeft,
100-
y: pointerEvent.pageY - boundingArea.top - scrollTop,
116+
x: x - boundingArea.left - scrollLeft,
117+
y: y - boundingArea.top - scrollTop,
101118
};
102119
},
103120
[],
@@ -106,62 +123,75 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
106123
/* Mouse Handlers - Mouse down, move and up */
107124

108125
const handlePointerDown = useCallback(
109-
(event: React.PointerEvent<HTMLDivElement>): void => {
126+
(event: TouchEvent | MouseEvent): void => {
127+
if (event instanceof TouchEvent && event.touches.length > 1) {
128+
return;
129+
}
130+
if (readOnly) return;
110131
// Allow only chosen pointer type
111132

112133
if (
113-
allowOnlyPointerType !== "all" &&
114-
event.pointerType !== allowOnlyPointerType
134+
allowOnlyPointerType !== "all"
115135
) {
116-
return;
136+
if (event instanceof TouchEvent) {
137+
const touch = event.touches[0] as TouchExtends;
138+
if (TOUCH_TYPE_MAP[touch.touchType] !== allowOnlyPointerType) {
139+
return;
140+
}
141+
}
117142
}
118143

119-
if (event.pointerType === "mouse" && event.button !== 0) return;
144+
event.preventDefault();
120145

121-
const isEraser =
122-
// eslint-disable-next-line no-bitwise
123-
event.pointerType === "pen" && (event.buttons & 32) === 32;
124146
const point = getCoordinates(event);
125147

126-
onPointerDown(point, isEraser);
148+
onPointerDown(point);
127149
},
128-
[allowOnlyPointerType, getCoordinates, onPointerDown],
150+
[allowOnlyPointerType, getCoordinates, onPointerDown, readOnly],
129151
);
130152

131153
const handlePointerMove = useCallback(
132-
(event: React.PointerEvent<HTMLDivElement>): void => {
133-
if (!isDrawing) return;
154+
(event: MouseEvent | TouchEvent): void => {
155+
event.preventDefault();
156+
if (!isDrawing || readOnly) return;
134157

135158
// Allow only chosen pointer type
136159
if (
137-
allowOnlyPointerType !== "all" &&
138-
event.pointerType !== allowOnlyPointerType
160+
allowOnlyPointerType !== "all"
139161
) {
140-
return;
162+
if (event instanceof TouchEvent) {
163+
const touch = event.touches[0] as TouchExtends;
164+
if (TOUCH_TYPE_MAP[touch.touchType] !== allowOnlyPointerType) {
165+
return;
166+
}
167+
}
141168
}
142169

143170
const point = getCoordinates(event);
144171

145172
onPointerMove(point);
146173
},
147-
[allowOnlyPointerType, getCoordinates, isDrawing, onPointerMove],
174+
[allowOnlyPointerType, getCoordinates, isDrawing, onPointerMove, readOnly],
148175
);
149176

150177
const handlePointerUp = useCallback(
151-
(event: React.PointerEvent<HTMLDivElement> | PointerEvent): void => {
152-
if (event.pointerType === "mouse" && event.button !== 0) return;
153-
178+
(event: TouchEvent | MouseEvent): void => {
179+
if (readOnly) return;
154180
// Allow only chosen pointer type
155181
if (
156-
allowOnlyPointerType !== "all" &&
157-
event.pointerType !== allowOnlyPointerType
182+
allowOnlyPointerType !== "all"
158183
) {
159-
return;
184+
if (event instanceof TouchEvent) {
185+
const touch = event.touches[0] as TouchExtends;
186+
if (TOUCH_TYPE_MAP[touch.touchType] !== allowOnlyPointerType) {
187+
return;
188+
}
189+
}
160190
}
161191

162192
onPointerUp();
163193
},
164-
[allowOnlyPointerType, onPointerUp],
194+
[allowOnlyPointerType, onPointerUp, readOnly],
165195
);
166196

167197
/* Mouse Handlers ends */
@@ -263,13 +293,33 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
263293
}));
264294

265295
/* Add event listener to Mouse up and Touch up to
266-
release drawing even when point goes out of canvas */
296+
release drawing even when point goes out of canvas */
267297
React.useEffect(() => {
268-
document.addEventListener("pointerup", handlePointerUp);
269-
return () => {
270-
document.removeEventListener("pointerup", handlePointerUp);
271-
};
272-
}, [handlePointerUp]);
298+
const el = canvasRef.current;
299+
if (el) {
300+
el.addEventListener("touchstart", handlePointerDown);
301+
el.addEventListener("mousedown", handlePointerDown);
302+
303+
el.addEventListener("touchmove", handlePointerMove);
304+
el.addEventListener("mousemove", handlePointerMove);
305+
306+
el.addEventListener("touchend", handlePointerUp);
307+
el.addEventListener("mouseup", handlePointerUp);
308+
document.addEventListener("mouseup", handlePointerUp);
309+
return () => {
310+
el.removeEventListener("touchstart", handlePointerDown);
311+
el.removeEventListener("mousedown", handlePointerDown);
312+
313+
el.removeEventListener("touchmove", handlePointerMove);
314+
el.removeEventListener("mousemove", handlePointerMove);
315+
316+
el.removeEventListener("touchend", handlePointerUp);
317+
el.removeEventListener("mouseup", handlePointerUp);
318+
document.removeEventListener("mouseup", handlePointerUp);
319+
};
320+
}
321+
return () => { }
322+
}, [handlePointerDown, handlePointerMove, handlePointerUp]);
273323

274324
const eraserPaths = React.useMemo(
275325
() => paths.filter((path) => !path.drawMode),
@@ -305,13 +355,12 @@ release drawing even when point goes out of canvas */
305355
className={className}
306356
style={{
307357
touchAction: "none",
358+
userSelect: "none",
359+
WebkitTouchCallout: "none",
308360
width,
309361
height,
310362
...style,
311363
}}
312-
onPointerDown={readOnly ? undefined : handlePointerDown}
313-
onPointerMove={readOnly ? undefined : handlePointerMove}
314-
onPointerUp={readOnly ? undefined : handlePointerUp}
315364
>
316365
<svg
317366
version="1.1"

packages/react-sketch-canvas/src/types/canvas.ts

+4
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ export interface CanvasPath {
6969
*/
7070
readonly endTimestamp?: number;
7171
}
72+
73+
export interface TouchExtends extends Touch {
74+
touchType: "direct" | " stylus";
75+
}

0 commit comments

Comments
 (0)