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

fix(Canvas): allowTouchScrolling interactions #10078

Merged
merged 11 commits into from
Oct 12, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [next]

- fix(Canvas): allowTouchScrolling interactions [#10078](https://github.com/fabricjs/fabric.js/pull/10078)
- update(IText): Add method enterEditingImpl/exitEditingImpl that executes the logic of enterEditing/exitEditing without events [#10187](https://github.com/fabricjs/fabric.js/issues/10187)
- fix(FabricObject): Fix clipPath blurryness with scale [#9774](https://github.com/fabricjs/fabric.js/pull/9774)

Expand Down
10 changes: 10 additions & 0 deletions e2e/tests/controls/touch-scrolling/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Rect } from 'fabric';
import { beforeAll } from '../../test';

beforeAll((canvas) => {
const rect = new Rect({ width: 50, height: 50, fill: 'blue' });
canvas.allowTouchScrolling = true;
canvas.setDimensions({ width: 600, height: 4000 });
canvas.add(rect);
return { rect };
});
114 changes: 114 additions & 0 deletions src/canvas/Canvas.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Canvas } from './Canvas';
import { Rect } from '../shapes/Rect';

describe('Canvas', () => {
describe('touchStart', () => {
test('will prevent default to not allow dom scrolling on canvas touch drag', () => {
const canvas = new Canvas(undefined, {
allowTouchScrolling: false,
});
const touch = {
clientX: 10,
clientY: 0,
identifier: 1,
target: canvas.upperCanvasEl,
};
const evt = new TouchEvent('touchstart', {
touches: [touch],
changedTouches: [touch],
});
evt.preventDefault = jest.fn();
canvas._onTouchStart(evt);
expect(evt.preventDefault).toHaveBeenCalled();
});
test('will not prevent default when allowTouchScrolling is true and there is no action', () => {
const canvas = new Canvas(undefined, {
allowTouchScrolling: true,
});
const touch = {
clientX: 10,
clientY: 0,
identifier: 1,
target: canvas.upperCanvasEl,
};
const evt = new TouchEvent('touchstart', {
touches: [touch],
changedTouches: [touch],
});
evt.preventDefault = jest.fn();
canvas._onTouchStart(evt);
expect(evt.preventDefault).not.toHaveBeenCalled();
});
test('will prevent default when allowTouchScrolling is true but we are drawing', () => {
const canvas = new Canvas(undefined, {
allowTouchScrolling: true,
isDrawingMode: true,
});
const touch = {
clientX: 10,
clientY: 0,
identifier: 1,
target: canvas.upperCanvasEl,
};
const evt = new TouchEvent('touchstart', {
touches: [touch],
changedTouches: [touch],
});
evt.preventDefault = jest.fn();
canvas._onTouchStart(evt);
expect(evt.preventDefault).toHaveBeenCalled();
});
test('will prevent default when allowTouchScrolling is true and we are dragging an object', () => {
const canvas = new Canvas(undefined, {
allowTouchScrolling: true,
});
const rect = new Rect({
width: 2000,
height: 2000,
left: -500,
top: -500,
});
canvas.add(rect);
canvas.setActiveObject(rect);
const touch = {
clientX: 10,
clientY: 0,
identifier: 1,
target: canvas.upperCanvasEl,
};
const evt = new TouchEvent('touchstart', {
touches: [touch],
changedTouches: [touch],
});
evt.preventDefault = jest.fn();
canvas._onTouchStart(evt);
expect(evt.preventDefault).toHaveBeenCalled();
});
test('will NOT prevent default when allowTouchScrolling is true and we just lost selection', () => {
const canvas = new Canvas(undefined, {
allowTouchScrolling: true,
});
const rect = new Rect({
width: 200,
height: 200,
left: 1000,
top: 1000,
});
canvas.add(rect);
canvas.setActiveObject(rect);
const touch = {
clientX: 10,
clientY: 0,
identifier: 1,
target: canvas.upperCanvasEl,
};
const evt = new TouchEvent('touchstart', {
touches: [touch],
changedTouches: [touch],
});
evt.preventDefault = jest.fn();
canvas._onTouchStart(evt);
expect(evt.preventDefault).not.toHaveBeenCalled();
});
});
});
29 changes: 22 additions & 7 deletions src/canvas/Canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,11 +597,24 @@ export class Canvas extends SelectableCanvas implements CanvasOptions {
* @param {Event} e Event object fired on mousedown
*/
_onTouchStart(e: TouchEvent) {
e.preventDefault();
// we will prevent scrolling if allowTouchScrolling is not enabled and
let shouldPreventScrolling = !this.allowTouchScrolling;
const currentActiveObject = this._activeObject;
if (this.mainTouchId === undefined) {
this.mainTouchId = this.getPointerId(e);
}
this.__onMouseDown(e);
// after executing fabric logic for mouse down let's see
// if we didn't change target or if we are drawing
// we want to prevent scrolling anyway
if (
this.isDrawingMode ||
(currentActiveObject && this._target === currentActiveObject)
) {
shouldPreventScrolling = true;
}
// prevent default, will block scrolling from start
shouldPreventScrolling && e.preventDefault();
this._resetTransformEventData();
const canvasElement = this.upperCanvasEl,
eventTypePrefix = this._getEventPrefix();
Expand All @@ -612,12 +625,14 @@ export class Canvas extends SelectableCanvas implements CanvasOptions {
this._onTouchEnd as EventListener,
addEventOptions,
);
addListener(
doc,
'touchmove',
this._onMouseMove as EventListener,
addEventOptions,
);
// if we scroll don't register the touch move event
shouldPreventScrolling &&
addListener(
doc,
'touchmove',
this._onMouseMove as EventListener,
addEventOptions,
);
// Unbind mousedown to prevent double triggers from touch devices
removeListener(
canvasElement,
Expand Down
3 changes: 3 additions & 0 deletions src/canvas/StaticCanvasOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ export interface StaticCanvasOptions

/**
* Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas
* It gives PRIORITY to DOM scrolling, it doesn't make it always possible.
* If is true, when using a touch event on the canvas, the canvas will scroll if scroll is possible.
* If we are in drawing mode or if we are selecting an object the canvas preventDefault and so it won't scroll
* @type Boolean
* @default
*
Expand Down
Loading