diff --git a/src/Carousel/Carousel.js b/src/Carousel/Carousel.js index 75b2cf1..86e9190 100644 --- a/src/Carousel/Carousel.js +++ b/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; } /** diff --git a/src/Carousel/scss/base.scss b/src/Carousel/scss/base.scss index 94a3ae4..f4b7cf3 100644 --- a/src/Carousel/scss/base.scss +++ b/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; } diff --git a/src/Fancybox/plugins/Image/Image.js b/src/Fancybox/plugins/Image/Image.js index 151e047..c3bfe88 100644 --- a/src/Fancybox/plugins/Image/Image.js +++ b/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; diff --git a/src/Fancybox/plugins/Image/Image.scss b/src/Fancybox/plugins/Image/Image.scss index 64eeb8a..29183a2 100644 --- a/src/Fancybox/plugins/Image/Image.scss +++ b/src/Fancybox/plugins/Image/Image.scss @@ -1,6 +1,5 @@ .fancybox__image { transform-origin: 0 0; - touch-action: none; user-select: none; transition: none; } diff --git a/src/Fancybox/scss/base.scss b/src/Fancybox/scss/base.scss index f46e6cd..0cc0d0b 100644 --- a/src/Fancybox/scss/base.scss +++ b/src/Fancybox/scss/base.scss @@ -115,7 +115,6 @@ body.compensate-for-scrollbar { outline: 0; overflow: auto; - -webkit-overflow-scrolling: touch; &::before, &::after { diff --git a/src/Panzoom/Panzoom.js b/src/Panzoom/Panzoom.js index fedfab6..79a56b8 100644 --- a/src/Panzoom/Panzoom.js +++ b/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; } diff --git a/src/Panzoom/scss/base.scss b/src/Panzoom/scss/base.scss index dc124d1..1c0979d 100644 --- a/src/Panzoom/scss/base.scss +++ b/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; } diff --git a/src/shared/utils/PointerTracker.js b/src/shared/utils/PointerTracker.js index 3f8a4d0..bae063c 100644 --- a/src/shared/utils/PointerTracker.js +++ b/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; } } diff --git a/src/shared/utils/clearTextSelection.js b/src/shared/utils/clearTextSelection.js deleted file mode 100644 index 85c963d..0000000 --- a/src/shared/utils/clearTextSelection.js +++ /dev/null @@ -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(); - } - } -}; diff --git a/src/shared/utils/isScrollable.js b/src/shared/utils/isScrollable.js index 2c7aeee..5cae806 100644 --- a/src/shared/utils/isScrollable.js +++ b/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; } diff --git a/tests/2_panzoom_test.js b/tests/2_panzoom_test.js index 49243b0..f76fb80 100644 --- a/tests/2_panzoom_test.js +++ b/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); }); diff --git a/tests/3_carousel_test.js b/tests/3_carousel_test.js index a655ccb..a40ff40 100644 --- a/tests/3_carousel_test.js +++ b/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);