index : pan-zoom | |
SVG pan/zoom library. |
aboutsummaryrefslogtreecommitdiff |
diff options
author | Catalin Mititiuc <webdevcat@proton.me> | 2024-06-11 14:58:29 -0700 |
---|---|---|
committer | Catalin Mititiuc <webdevcat@proton.me> | 2024-06-11 15:09:41 -0700 |
commit | 9c34e15c47cf3578adeff41693a62061a25fdcde (patch) | |
tree | fed30aa9d385c5be893f5715791837583d480350 /src | |
parent | 2d3fc1cd22ffcc61ec178eeaf97f3a4d7cba98bf (diff) |
Update implementation to account for WebKit bugv0.2.0
getScreenCTM() on WebKit does not reflect transformations applied to an ancestor (see bug https://bugs.webkit.org/show_bug.cgi?id=209220), so instead of transforming the root <svg> element, we can only transform a child element
Diffstat (limited to 'src')
-rw-r--r-- | src/app.js | 28 | ||||
-rw-r--r-- | src/modules/pan.js | 37 | ||||
-rw-r--r-- | src/modules/zoom.js | 30 |
3 files changed, 50 insertions, 45 deletions
@@ -2,14 +2,7 @@ import zoom from './modules/zoom.js'; import pan from './modules/pan.js'; const optionalZoomFactor = 0.1, - container = document.querySelector('.container'), - object = document.querySelector('object'), - img = document.querySelector('img'), - button = document.querySelector('button'); - -function reset(elements) { - elements.forEach(el => el.removeAttribute('style')); -} + object = document.querySelector('object'); // If embedding an SVG using an <object> tag, it's necessary to wait until the // page has loaded before querying its `contentDocument`, otherwise it will be @@ -17,15 +10,18 @@ function reset(elements) { window.addEventListener('load', function () { const svg = object.contentDocument.querySelector('svg'), - pannableAndZoomableElements = [img, svg]; + targetEl = svg.querySelector('g'), + pointer = svg.querySelector('#pointer'), + options = { passive: false }; - button.addEventListener('click', () => { - [button, container].forEach(el => el.classList.toggle('switch')); - reset(pannableAndZoomableElements); - }); + svg.addEventListener('wheel', e => zoom(targetEl, e, optionalZoomFactor), options); + svg.addEventListener('pointerdown', e => pan(svg, targetEl, e), options); + + svg.addEventListener('pointermove', e => { + const pt = new DOMPoint(e.clientX, e.clientY), + svgP = pt.matrixTransform(targetEl.getScreenCTM().inverse()); - pannableAndZoomableElements.forEach(el => { - el.addEventListener('wheel', e => zoom(el, e, optionalZoomFactor), { passive: false }); - el.addEventListener('pointerdown', e => pan(el, e), { passive: false }); + pointer.setAttributeNS(null, 'cx', svgP.x); + pointer.setAttributeNS(null, 'cy', svgP.y); }); }); diff --git a/src/modules/pan.js b/src/modules/pan.js index 201c2f1..6f2cacf 100644 --- a/src/modules/pan.js +++ b/src/modules/pan.js @@ -2,13 +2,6 @@ import getComputedTransformMatrix from './utils.js'; const minDistanceThreshold = 5; -function setToCurrentPointerCoords(point, e) { - point.x = e.clientX; - point.y = e.clientY; - - return point; -} - function distanceBetween({ x: x1, y: y1 }, { x: x2, y: y2 }) { return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); } @@ -27,35 +20,45 @@ function getTranslateMatrix(startPt, movePt) { return translateMatrix.translate(movePt.x - startPt.x, movePt.y - startPt.y); } -export default function (el, e) { +export default function (svg, el, e) { e.preventDefault(); const mtx = getComputedTransformMatrix(el), - startPt = new DOMPoint(e.clientX, e.clientY), - movePt = new DOMPoint(); + inverseScreenCTM = el.getScreenCTM().inverse(); - let isPanning = false; + let startPt = new DOMPoint(e.clientX, e.clientY), + movePt = new DOMPoint(), + isPanning = false; function pointerMove(e) { - setToCurrentPointerCoords(movePt, e); + movePt.x = e.clientX; + movePt.y = e.clientY; if (!isPanning && minDistanceThresholdIsMet(startPt, movePt)) { isPanning = true; e.target.setPointerCapture(e.pointerId); - setToCurrentPointerCoords(startPt, e); + + startPt.x = e.clientX; + startPt.y = e.clientY; + startPt = startPt.matrixTransform(inverseScreenCTM); + stopEventPropagationToChildren(el, 'click'); } if (isPanning) { - el.style.transform = getTranslateMatrix(startPt, movePt).multiply(mtx); + movePt.x = e.clientX; + movePt.y = e.clientY; + movePt = movePt.matrixTransform(inverseScreenCTM); + + el.style.transform = mtx.multiply(getTranslateMatrix(startPt, movePt)); } } - el.addEventListener('pointermove', pointerMove); + svg.addEventListener('pointermove', pointerMove); - el.addEventListener( + svg.addEventListener( 'pointerup', - () => el.removeEventListener('pointermove', pointerMove), + () => svg.removeEventListener('pointermove', pointerMove), { once: true } ); } diff --git a/src/modules/zoom.js b/src/modules/zoom.js index 97a23e1..fc0540b 100644 --- a/src/modules/zoom.js +++ b/src/modules/zoom.js @@ -8,35 +8,41 @@ function getScale(e, factor) { return zoomIn(e.deltaY) ? 1 + factor : 1 - factor; } -function getFocalPointBeforeTransform(el, e) { - const { x, y, width, height } = el.getBoundingClientRect(); +function getFocalPointBeforeTransform(el, e, inverseScreenCTM) { + const { x, y, width, height } = el.getBoundingClientRect(), + pointer = (new DOMPoint(e.clientX, e.clientY)).matrixTransform(inverseScreenCTM), + origin = (new DOMPoint(x, y)).matrixTransform(inverseScreenCTM), + terminus = (new DOMPoint(x + width, y + height)).matrixTransform(inverseScreenCTM); return { - x: e.clientX, - y: e.clientY, + x: pointer.x, + y: pointer.y, relativeToImageSize: { - x: (e.clientX - x) / width, - y: (e.clientY - y) / height + x: (pointer.x - origin.x) / (terminus.x - origin.x), + y: (pointer.y - origin.y) / (terminus.y - origin.y) } }; } -function getFocalPointAfterTransform(el, fpBeforeTrans) { +function getFocalPointAfterTransform(el, fpBeforeTrans, inverseScreenCTM) { const { x, y, width, height } = el.getBoundingClientRect(), + origin = (new DOMPoint(x, y)).matrixTransform(inverseScreenCTM), + terminus = (new DOMPoint(x + width, y + height)).matrixTransform(inverseScreenCTM), relativeFocalPoint = fpBeforeTrans.relativeToImageSize; return { - x: x + width * relativeFocalPoint.x, - y: y + height * relativeFocalPoint.y + x: origin.x + (terminus.x - origin.x) * relativeFocalPoint.x, + y: origin.y + (terminus.y - origin.y) * relativeFocalPoint.y }; } function getTranslateMatrix(el, e, scaleMatrix) { - const fpBeforeTrans = getFocalPointBeforeTransform(el, e); + const inverseScreenCTM = el.getScreenCTM().inverse(), + fpBeforeTrans = getFocalPointBeforeTransform(el, e, inverseScreenCTM); el.style.transform = scaleMatrix; - const fpAfterTrans = getFocalPointAfterTransform(el, fpBeforeTrans), + const fpAfterTrans = getFocalPointAfterTransform(el, fpBeforeTrans, inverseScreenCTM), translateMatrix = new DOMMatrix(); return translateMatrix.translate( @@ -52,5 +58,5 @@ export default function (el, e, factor = 0.1) { scale = getScale(e, factor), transMtx = getTranslateMatrix(el, e, mtx.scale(scale)); - el.style.transform = transMtx.multiply(mtx).scale(scale); + el.style.transform = mtx.multiply(transMtx).scale(scale); } |