Repository: fabricjs/fabric.js (ts)

Issue:
TOUCH and TOUCHMOVE related problems.
I have no touch device for debugging.
I m in the process of getting one or buying one.

I reference here all the issue to verify and fix.
Please add here the new issues related to touch.
allowTouchScrolling doesn't work
<!-- BUG TEMPLATE -->
## Version
3.4.0

## Test Case
https://codepen.io/den-bogdanov/pen/eYOwbPj

## Information about the environment
mobile browser chrome, safari

## Steps to reproduce
Open test case and try to scroll with help device emulation in devtools or from a mobile device

## Expected Behavior
Must be scrolling

## Actual Behavior
No touch scrolling 

Pull request:
fix(Canvas): allowTouchScrolling interactions
## Description

The current canvas.allowTouchScrolling doesn't really work.
close #5903 
close #3869
close #9724

This PR changes the behaviour as follow:

### allowTouchScrolling enabled set to true

Priority is given to enable scroll on canvas with touch event.
A touchStart event will not be prevented unless we are in drawing mode or we confirm the selection of an object.

If you are drawing, never scroll
if you select an object in this action, scroll
if you deselect the object, scroll
if you click the same object that was already selected, do not scroll.

If you want both touch scrolling and drag objects around, you will have to select with an event and drag the object with another event.

In a mobile device with allowTouchScrolling se to true multi selection by dragging won't work.

When the touchStart is NOT PREVENTED and so a scroll action start, the touch move events are ignore and won't trigger any canvas specific event.

Everything is let to the browser until touchUp fires

### allowTouchScrolling disabled

Everything continue as normal

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d1efa904..2a69a02a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
 
diff --git a/src/canvas/Canvas.spec.ts b/src/canvas/Canvas.spec.ts
new file mode 100644
index 000000000..abd4c2da7
--- /dev/null
+++ b/src/canvas/Canvas.spec.ts
@@ -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();
+    });
+  });
+});
diff --git a/src/canvas/Canvas.ts b/src/canvas/Canvas.ts
index 2a5fb90e5..108f8d39b 100644
--- a/src/canvas/Canvas.ts
+++ b/src/canvas/Canvas.ts
@@ -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();
@@ -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,
diff --git a/src/canvas/StaticCanvasOptions.ts b/src/canvas/StaticCanvasOptions.ts
index f544088e8..c999665fb 100644
--- a/src/canvas/StaticCanvasOptions.ts
+++ b/src/canvas/StaticCanvasOptions.ts
@@ -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
    *
diff --git a/src/shapes/Object/Object.ts b/src/shapes/Object/Object.ts
index 5e364b3f2..28379bba7 100644
--- a/src/shapes/Object/Object.ts
+++ b/src/shapes/Object/Object.ts
@@ -349,6 +349,7 @@ export class FabricObject<
    * Legacy identifier of the class. Prefer using utils like isType or instanceOf
    * Will be removed in fabric 7 or 8.
    * The setter exists to avoid type errors in old code and possibly current deserialization code.
+   * DO NOT build new code around this type value
    * @TODO add sustainable warning message
    * @type string
    * @deprecated


Test patch:
diff --git a/e2e/tests/controls/touch-scrolling/index.ts b/e2e/tests/controls/touch-scrolling/index.ts
new file mode 100644
index 000000000..400fbc462
--- /dev/null
+++ b/e2e/tests/controls/touch-scrolling/index.ts
@@ -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 };
+});