Update implementation to account for WebKit bug
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
This commit is contained in:
28
src/app.js
28
src/app.js
@@ -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);
|
||||
|
||||
pannableAndZoomableElements.forEach(el => {
|
||||
el.addEventListener('wheel', e => zoom(el, e, optionalZoomFactor), { passive: false });
|
||||
el.addEventListener('pointerdown', e => pan(el, e), { passive: false });
|
||||
svg.addEventListener('pointermove', e => {
|
||||
const pt = new DOMPoint(e.clientX, e.clientY),
|
||||
svgP = pt.matrixTransform(targetEl.getScreenCTM().inverse());
|
||||
|
||||
pointer.setAttributeNS(null, 'cx', svgP.x);
|
||||
pointer.setAttributeNS(null, 'cy', svgP.y);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user