Browse Source

Switch away from pointer events

pull/243/head
Jānis Skarnelis 2 years ago
parent
commit
055224f0a3
  1. 2
      src/Carousel/Carousel.js
  2. 11
      src/Carousel/scss/base.scss
  3. 4
      src/Fancybox/plugins/Image/Image.js
  4. 1
      src/Fancybox/plugins/Image/Image.scss
  5. 1
      src/Fancybox/scss/base.scss
  6. 38
      src/Panzoom/Panzoom.js
  7. 1
      src/Panzoom/scss/base.scss
  8. 129
      src/shared/utils/PointerTracker.js
  9. 14
      src/shared/utils/clearTextSelection.js
  10. 4
      src/shared/utils/isScrollable.js
  11. 109
      tests/2_panzoom_test.js
  12. 22
      tests/3_carousel_test.js

2
src/Carousel/Carousel.js

@ -84,6 +84,8 @@ export class Carousel extends Base {
this.slidePrev = throttle(this.slidePrev.bind(this), 250, true);
this.init();
$container.__Carousel = this;
}
/**

11
src/Carousel/scss/base.scss

@ -1,10 +1,3 @@
.not-selectable {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.#{$carousel-prefix}carousel {
position: relative;
box-sizing: border-box;
@ -48,8 +41,4 @@
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
/* https://github.com/andreszs/jquery.events.swipe#pointer-event-remarks */
touch-action: pan-y;
}

4
src/Fancybox/plugins/Image/Image.js

@ -571,7 +571,9 @@ export class Image {
switch (this.fancybox.option("Image.wheel")) {
case "zoom":
slide.Panzoom && slide.Panzoom.zoomWithWheel(event);
if (slide.state === "done") {
slide.Panzoom && slide.Panzoom.zoomWithWheel(event);
}
break;

1
src/Fancybox/plugins/Image/Image.scss

@ -1,6 +1,5 @@
.fancybox__image {
transform-origin: 0 0;
touch-action: none;
user-select: none;
transition: none;
}

1
src/Fancybox/scss/base.scss

@ -115,7 +115,6 @@ body.compensate-for-scrollbar {
outline: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
&::before,
&::after {

38
src/Panzoom/Panzoom.js

@ -1,5 +1,6 @@
import { extend } from "../shared/utils/extend.js";
import { round } from "../shared/utils/round.js";
import { isScrollable } from "../shared/utils/isScrollable.js";
import { ResizeObserver } from "../shared/utils/ResizeObserver.js";
import { PointerTracker, getMidpoint, getDistance } from "../shared/utils/PointerTracker.js";
@ -126,6 +127,8 @@ export class Panzoom extends Base {
friction: 0,
});
}
$container.__Panzoom = this;
}
/**
@ -348,7 +351,7 @@ export class Panzoom extends Base {
}
if (this.velocity.scale < 0) {
return;
return false;
}
if (!pointerTracker.currentPointers.length) {
@ -365,6 +368,10 @@ export class Panzoom extends Base {
}
}
if (isScrollable(event.target)) {
return false;
}
if (this.trigger("touchStart", event) === false) {
return false;
}
@ -383,7 +390,7 @@ export class Panzoom extends Base {
return;
}
if (this.trigger("touchMove", event) == false) {
if (this.trigger("touchMove", event) === false) {
event.preventDefault();
return;
}
@ -391,7 +398,7 @@ export class Panzoom extends Base {
// Disable touch action if current zoom level is below base level
if (
currentPointers.length < 2 &&
this.option("panOnlyZoomed") == true &&
this.option("panOnlyZoomed") === true &&
this.content.width <= this.viewport.width &&
this.content.height <= this.viewport.height &&
this.transform.scale <= this.option("baseScale")
@ -403,9 +410,6 @@ export class Panzoom extends Base {
return;
}
event.preventDefault();
event.stopPropagation();
const prevMidpoint = getMidpoint(previousPointers[0], previousPointers[1]);
const newMidpoint = getMidpoint(currentPointers[0], currentPointers[1]);
@ -415,7 +419,7 @@ export class Panzoom extends Base {
const prevDistance = getDistance(previousPointers[0], previousPointers[1]);
const newDistance = getDistance(currentPointers[0], currentPointers[1]);
const scaleDiff = prevDistance ? newDistance / prevDistance : 1;
const scaleDiff = prevDistance && newDistance ? newDistance / prevDistance : 1;
this.dragOffset.x += panX;
this.dragOffset.y += panY;
@ -428,18 +432,28 @@ export class Panzoom extends Base {
if (axisToLock && !this.lockAxis) {
if (Math.abs(this.dragOffset.x) < 6 && Math.abs(this.dragOffset.y) < 6) {
event.preventDefault();
return;
}
if (axisToLock === "xy") {
const angle = Math.abs((Math.atan2(this.dragOffset.y, this.dragOffset.x) * 180) / Math.PI);
const angle = Math.abs((Math.atan2(this.dragOffset.y, this.dragOffset.x) * 180) / Math.PI);
this.lockAxis = angle > 45 && angle < 135 ? "y" : "x";
} else {
this.lockAxis = axisToLock;
this.lockAxis = angle > 45 && angle < 135 ? "y" : "x";
}
if (axisToLock !== "xy" && this.lockAxis === "y") {
if (event.type === "mousemove") {
event.preventDefault();
}
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
if (this.lockAxis) {
this.dragOffset[this.lockAxis === "x" ? "y" : "x"] = 0;
}

1
src/Panzoom/scss/base.scss

@ -29,7 +29,6 @@
transform-origin: 0 0;
transition: none;
/* Disable default manipulations by a touchscreen user. For example, zooming features built into the browser */
touch-action: none;
user-select: none;
}

129
src/shared/utils/PointerTracker.js

@ -1,28 +1,24 @@
import { clearTextSelection } from "./clearTextSelection.js";
class Pointer {
constructor(nativePointer) {
this.id = nativePointer.pointerId || nativePointer.identifier || -1;
this.id = self.Touch && nativePointer instanceof Touch ? nativePointer.identifier : -1;
this.pageX = nativePointer.pageX;
this.pageY = nativePointer.pageY;
this.clientX = nativePointer.clientX;
this.clientY = nativePointer.clientY;
this.nativePointer = nativePointer;
}
}
function getDistance(a, b) {
const getDistance = (a, b) => {
if (!b) {
return 0;
}
return Math.sqrt((b.clientX - a.clientX) ** 2 + (b.clientY - a.clientY) ** 2);
}
};
function getMidpoint(a, b) {
const getMidpoint = (a, b) => {
if (!b) {
return a;
}
@ -31,51 +27,51 @@ function getMidpoint(a, b) {
clientX: (a.clientX + b.clientX) / 2,
clientY: (a.clientY + b.clientY) / 2,
};
}
};
const isTouchEvent = (event) => "changedTouches" in event;
class PointerTracker {
constructor(element, { start = () => true, move = () => {}, end = () => {} } = {}) {
this.element = element;
constructor(_element, { start = () => true, move = () => {}, end = () => {} } = {}) {
this._element = _element;
this.startPointers = [];
this.currentPointers = [];
this.startCallback = start;
this.moveCallback = move;
this.endCallback = end;
this.onStart = (event) => {
if (event.button && event.button !== 0) {
this._pointerStart = (event) => {
if (!(event.buttons & 1)) {
return;
}
const pointer = new Pointer(event);
if (this.startCallback(pointer, event) === false) {
return false;
if (this.currentPointers.some((p) => p.id === pointer.id)) {
return;
}
event.preventDefault();
clearTextSelection();
this.currentPointers.push(pointer);
this.startPointers.push(pointer);
const capturingElement = event.target && "setPointerCapture" in event.target ? event.target : this.element;
if (!this._triggerPointerStart(pointer, event)) {
return;
}
capturingElement.setPointerCapture(event.pointerId);
window.addEventListener("mousemove", this._move, { passive: false });
window.addEventListener("mouseup", this._pointerEnd, { passive: false });
};
this.element.addEventListener("pointermove", this.onMove);
this.element.addEventListener("pointerup", this.onEnd);
this.element.addEventListener("pointercancel", this.onEnd);
this._touchStart = (event) => {
for (const touch of Array.from(event.changedTouches || [])) {
this._triggerPointerStart(new Pointer(touch), event);
}
};
this.onMove = (event) => {
this._move = (event) => {
const previousPointers = this.currentPointers.slice();
const changedPointers = isTouchEvent(event)
? Array.from(event.changedTouches).map((t) => new Pointer(t))
: [new Pointer(event)];
const trackedChangedPointers = [];
for (const pointer of [new Pointer(event)]) {
for (const pointer of changedPointers) {
const index = this.currentPointers.findIndex((p) => p.id === pointer.id);
if (index < 0) {
@ -87,39 +83,74 @@ class PointerTracker {
this.currentPointers[index] = pointer;
}
if (trackedChangedPointers.length) {
this.moveCallback(previousPointers, this.currentPointers, event);
}
this._moveCallback(previousPointers, this.currentPointers.slice(), event);
};
this.onEnd = (event) => {
const pointer = new Pointer(event);
this._triggerPointerEnd = (pointer, event) => {
if (!isTouchEvent(event) && event.buttons & 1) {
return false;
}
const index = this.currentPointers.findIndex((p) => p.id === pointer.id);
if (index === -1) {
if (index < 0) {
return false;
}
this.currentPointers.splice(index, 1);
this.startPointers.splice(index, 1);
this.endCallback(pointer, event);
this._endCallback(pointer, event);
return true;
};
this._pointerEnd = (event) => {
if (!this._triggerPointerEnd(new Pointer(event), event)) {
return;
}
window.removeEventListener("mousemove", this._move);
window.removeEventListener("mouseup", this._pointerEnd);
};
if (!this.currentPointers.length) {
this.element.removeEventListener("pointermove", this.onMove);
this.element.removeEventListener("pointerup", this.onEnd);
this.element.removeEventListener("pointercancel", this.onEnd);
this._touchEnd = (event) => {
for (const touch of Array.from(event.changedTouches || [])) {
this._triggerPointerEnd(new Pointer(touch), event);
}
};
this.element.addEventListener("pointerdown", this.onStart);
this._startCallback = start;
this._moveCallback = move;
this._endCallback = end;
this._element.addEventListener("mousedown", this._pointerStart, { passive: true });
this._element.addEventListener("touchstart", this._touchStart, { passive: true });
this._element.addEventListener("touchmove", this._move, { passive: false });
this._element.addEventListener("touchend", this._touchEnd);
this._element.addEventListener("touchcancel", this._touchEnd);
}
stop() {
this.element.removeEventListener("pointerdown", this.onStart);
this.element.removeEventListener("pointermove", this.onMove);
this.element.removeEventListener("pointerup", this.onEnd);
this.element.removeEventListener("pointercancel", this.onEnd);
this._element.removeEventListener("mousedown", this._pointerStart, { passive: true });
this._element.removeEventListener("touchstart", this._touchStart, { passive: true });
this._element.removeEventListener("touchmove", this._move, { passive: false });
this._element.removeEventListener("touchend", this._touchEnd);
this._element.removeEventListener("touchcancel", this._touchEnd);
window.removeEventListener("mousemove", this._move, { passive: false });
window.removeEventListener("mouseup", this._pointerEnd, { passive: false });
}
_triggerPointerStart(pointer, event) {
if (!this._startCallback(pointer, event)) {
return false;
}
this.currentPointers.push(pointer);
this.startPointers.push(pointer);
return true;
}
}

14
src/shared/utils/clearTextSelection.js

@ -1,14 +0,0 @@
/**
* Deselect any text which may be selected on a page
*/
export const clearTextSelection = () => {
const selection = window.getSelection ? window.getSelection() : document.selection;
if (selection && selection.rangeCount && selection.getRangeAt(0).getClientRects().length) {
if (selection.removeAllRanges) {
selection.removeAllRanges();
} else if (selection.empty) {
selection.empty();
}
}
};

4
src/shared/utils/isScrollable.js

@ -22,6 +22,10 @@ export const isScrollable = function (node) {
return false;
}
if (node.__Panzoom) {
return false;
}
if (hasScrollbars(node)) {
return node;
}

109
tests/2_panzoom_test.js

@ -230,22 +230,21 @@ describe("Panzoom", function () {
destroyInstance(instance);
});
it("is draggable using pointer events", async function () {
it("is draggable using pointing device", async function () {
const instance = await createInstanceWithImage({}, true);
const x = instance.$content.getClientRects()[0].left;
const y = instance.$content.getClientRects()[0].top;
triggerEvent(instance.$container, "pointerdown", {
pointerId: 1,
triggerEvent(instance.$container, "mousedown", {
buttons: 1,
clientX: x + 60,
clientY: y + 60,
});
expect(instance.$content.style.transform).to.equal("translate3d(-37.5px, -25px, 0px) scale(1)");
triggerEvent(instance.$container, "pointermove", {
pointerId: 1,
triggerEvent(instance.$container, "mousemove", {
clientX: x + 40,
clientY: y + 60,
});
@ -259,10 +258,48 @@ describe("Panzoom", function () {
expect(instance.content.x).to.be.closeTo(-57.5, 0.5);
expect(instance.content.y).to.equal(-25);
triggerEvent(instance.$container, "pointerup", {
pointerId: 1,
triggerEvent(instance.$container, "mouseup", {});
destroyInstance(instance);
});
it("is draggable using touch device", async function () {
const instance = await createInstanceWithImage({}, true);
const x = instance.$content.getClientRects()[0].left;
const y = instance.$content.getClientRects()[0].top;
triggerEvent(instance.$container, "touchstart", {
changedTouches: [
{
clientX: x + 60,
clientY: y + 60,
},
],
});
expect(instance.$content.style.transform).to.equal("translate3d(-37.5px, -25px, 0px) scale(1)");
triggerEvent(instance.$container, "touchmove", {
changedTouches: [
{
clientX: x + 40,
clientY: y + 60,
},
],
});
await delay(250);
// It should be moved
expect(instance.dragOffset.x).to.equal(-20);
expect(instance.dragOffset.y).to.equal(0);
expect(instance.content.x).to.be.closeTo(-57.5, 0.5);
expect(instance.content.y).to.equal(-25);
triggerEvent(instance.$container, "touchend", {});
destroyInstance(instance);
});
@ -277,16 +314,15 @@ describe("Panzoom", function () {
const x = instance.$content.getClientRects()[0].left;
const y = instance.$content.getClientRects()[0].top;
triggerEvent(instance.$container, "pointerdown", {
pointerId: 1,
triggerEvent(instance.$container, "mousedown", {
buttons: 1,
clientX: x,
clientY: y + 50,
});
await delay();
triggerEvent(instance.$container, "pointermove", {
pointerId: 1,
triggerEvent(instance.$container, "mousemove", {
clientX: x + 300,
clientY: y + 50,
});
@ -308,9 +344,7 @@ describe("Panzoom", function () {
});
// This will Start pull-back animation
triggerEvent(instance.$container, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.$container, "mouseup", {});
});
expect(result.content.x).to.be.closeTo(0, 0.5);
@ -326,16 +360,15 @@ describe("Panzoom", function () {
const x = instance.$content.getClientRects()[0].left;
const y = instance.$content.getClientRects()[0].top;
triggerEvent(instance.$container, "pointerdown", {
pointerId: 1,
triggerEvent(instance.$container, "mousedown", {
buttons: 1,
clientX: x + 50,
clientY: y + 50,
});
await delay();
triggerEvent(instance.$container, "pointermove", {
pointerId: 1,
triggerEvent(instance.$container, "mousemove", {
clientX: x + 30,
clientY: y + 30,
});
@ -346,9 +379,7 @@ describe("Panzoom", function () {
expect(instance.content.x).to.be.closeTo(-20, 0.5);
expect(instance.content.y).to.be.equal(25);
triggerEvent(instance.$container, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.$container, "mouseup", {});
destroyInstance(instance);
});
@ -359,14 +390,13 @@ describe("Panzoom", function () {
const x = instance.$content.getClientRects()[0].left;
const y = instance.$content.getClientRects()[0].top;
triggerEvent(instance.$container, "pointerdown", {
pointerId: 1,
triggerEvent(instance.$container, "mousedown", {
buttons: 1,
clientX: x + 50,
clientY: y + 50,
});
triggerEvent(instance.$container, "pointermove", {
pointerId: 1,
triggerEvent(instance.$container, "mousemove", {
clientX: x + 45,
clientY: y + 40,
});
@ -377,9 +407,7 @@ describe("Panzoom", function () {
expect(instance.content.y).to.be.closeTo(22, 0.5);
expect(instance.content.x).to.equal(0);
triggerEvent(instance.$container, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.$container, "mouseup", {});
destroyInstance(instance);
});
@ -502,14 +530,13 @@ describe("Panzoom", function () {
const x = instance.$content.getClientRects()[0].left;
const y = instance.$content.getClientRects()[0].top;
triggerEvent(instance.$content, "pointerdown", {
pointerId: 1,
triggerEvent(instance.$content, "mousedown", {
buttons: 1,
clientX: x + 40,
clientY: y + 40,
});
triggerEvent(instance.$content, "pointermove", {
pointerId: 1,
triggerEvent(instance.$content, "mousemove", {
clientX: x + 20,
clientY: y + 20,
});
@ -520,9 +547,7 @@ describe("Panzoom", function () {
expect(instance.content.x).to.be.closeTo(-6, 1);
expect(instance.content.y).to.be.closeTo(-6, 1);
triggerEvent(instance.$container, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.$container, "mouseup", {});
// Wait for pull-back animation
await delay(300);
@ -547,13 +572,11 @@ describe("Panzoom", function () {
const y = instance.$content.getClientRects()[0].top;
triggerEvent(instance.$content, "pointerdown", {
pointerId: 1,
clientX: x + 50,
clientY: y + 50,
});
triggerEvent(instance.$content, "pointermove", {
pointerId: 1,
clientX: x + 10,
clientY: y + 10,
});
@ -564,9 +587,7 @@ describe("Panzoom", function () {
expect(instance.content.x).to.be.equal(0);
expect(instance.content.y).to.be.equal(0);
triggerEvent(instance.$content, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.$content, "pointerup", {});
destroyInstance(instance);
});
@ -581,9 +602,7 @@ describe("Panzoom", function () {
expect(instance.content.x).to.be.equal(0);
expect(instance.content.y).to.be.equal(0);
triggerEvent(instance.$content, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.$content, "pointerup", {});
destroyInstance(instance);
});
@ -598,9 +617,7 @@ describe("Panzoom", function () {
expect(instance.content.x).to.be.equal(10);
expect(instance.content.y).to.be.equal(10);
triggerEvent(instance.$content, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.$content, "pointerup", {});
destroyInstance(instance);
});

22
tests/3_carousel_test.js

@ -580,23 +580,20 @@ describe("Carousel", function () {
const x = instance.Panzoom.$content.getClientRects()[0].left;
const y = instance.Panzoom.$content.getClientRects()[0].top;
triggerEvent(instance.Panzoom.$container, "pointerdown", {
pointerId: 1,
triggerEvent(instance.Panzoom.$container, "mousedown", {
buttons: 1,
clientX: x + 150,
clientY: y + 50,
});
triggerEvent(instance.Panzoom.$container, "pointermove", {
pointerId: 1,
triggerEvent(instance.Panzoom.$container, "mousemove", {
clientX: x + 50,
clientY: y + 50,
});
await delay(300);
triggerEvent(instance.Panzoom.$container, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.Panzoom.$container, "mouseup", {});
expect(instance.page).to.equal(1);
@ -618,23 +615,20 @@ describe("Carousel", function () {
const x = instance.Panzoom.$content.getClientRects()[0].left;
const y = instance.Panzoom.$content.getClientRects()[0].top;
triggerEvent(instance.Panzoom.$container, "pointerdown", {
pointerId: 1,
triggerEvent(instance.Panzoom.$container, "mousedown", {
buttons: 1,
clientX: x + 150,
clientY: y + 50,
});
triggerEvent(instance.Panzoom.$container, "pointermove", {
pointerId: 1,
triggerEvent(instance.Panzoom.$container, "mousemove", {
clientX: x + 50,
clientY: y + 50,
});
await delay(300);
triggerEvent(instance.Panzoom.$container, "pointerup", {
pointerId: 1,
});
triggerEvent(instance.Panzoom.$container, "mouseup", {});
expect(instance.page).to.equal(1);

Loading…
Cancel
Save