index : pan-zoom | |
SVG pan/zoom library. |
aboutsummaryrefslogtreecommitdiff |
diff options
-rw-r--r-- | index.js | 2 | ||||
-rw-r--r-- | src/modules/pan.js | 79 | ||||
-rw-r--r-- | src/modules/zoom.js | 43 |
3 files changed, 78 insertions, 46 deletions
@@ -1,2 +1,2 @@ -export { default as pan } from './src/modules/pan.js'; +export { default as pan, programmaticPan } from './src/modules/pan.js'; export { default as zoom } from './src/modules/zoom.js'; diff --git a/src/modules/pan.js b/src/modules/pan.js index 6f2cacf..4e6485c 100644 --- a/src/modules/pan.js +++ b/src/modules/pan.js @@ -2,11 +2,15 @@ import getComputedTransformMatrix from './utils.js'; const minDistanceThreshold = 5; +function mainButtonPressed(e) { + return e.button === 0; +} + function distanceBetween({ x: x1, y: y1 }, { x: x2, y: y2 }) { return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); } -function minDistanceThresholdIsMet(startPt, endPt) { +function exceedsMinDistanceThreshhold(startPt, endPt) { return distanceBetween(startPt, endPt) >= minDistanceThreshold; } @@ -20,45 +24,68 @@ function getTranslateMatrix(startPt, movePt) { return translateMatrix.translate(movePt.x - startPt.x, movePt.y - startPt.y); } -export default function (svg, el, e) { - e.preventDefault(); +function getTransformMatrices(el) { + return { + computed: getComputedTransformMatrix(el), + inverseScreen: el.getScreenCTM().inverse() + }; +} - const mtx = getComputedTransformMatrix(el), - inverseScreenCTM = el.getScreenCTM().inverse(); +function clientToSvgPt({ clientX, clientY }, { inverseScreen }, pt = new DOMPoint()) { + pt.x = clientX; + pt.y = clientY; + return pt.matrixTransform(inverseScreen); +} - let startPt = new DOMPoint(e.clientX, e.clientY), - movePt = new DOMPoint(), - isPanning = false; +function setPanTransform(el, { computed }, startPt, endPt) { + el.style.transform = computed.multiply(getTranslateMatrix(startPt, endPt)); +} + +export function programmaticPan(el, from, to) { + const matrices = getTransformMatrices(el); + const startPt = clientToSvgPt(from, matrices); + const endPt = clientToSvgPt(to, matrices); + + el.style.transition = 'transform 0.5s'; + setPanTransform(el, matrices, startPt, endPt); + el.addEventListener('transitionend', () => el.style.transition = '', { once: true }); +} + +export default function (el) { + let matrices, startPt, movePt, isPanning; function pointerMove(e) { movePt.x = e.clientX; movePt.y = e.clientY; - if (!isPanning && minDistanceThresholdIsMet(startPt, movePt)) { + if (!isPanning && exceedsMinDistanceThreshhold(startPt, movePt)) { isPanning = true; - e.target.setPointerCapture(e.pointerId); - - startPt.x = e.clientX; - startPt.y = e.clientY; - startPt = startPt.matrixTransform(inverseScreenCTM); - + startPt = clientToSvgPt(e, matrices, startPt); stopEventPropagationToChildren(el, 'click'); } if (isPanning) { - movePt.x = e.clientX; - movePt.y = e.clientY; - movePt = movePt.matrixTransform(inverseScreenCTM); - - el.style.transform = mtx.multiply(getTranslateMatrix(startPt, movePt)); + movePt = clientToSvgPt(e, matrices, movePt); + setPanTransform(el, matrices, startPt, movePt); } } - svg.addEventListener('pointermove', pointerMove); + return function(e) { + if (!mainButtonPressed(e)) return; + e.preventDefault(); + e.target.setPointerCapture(e.pointerId); - svg.addEventListener( - 'pointerup', - () => svg.removeEventListener('pointermove', pointerMove), - { once: true } - ); + isPanning = false; + matrices = getTransformMatrices(el); + startPt = new DOMPoint(e.clientX, e.clientY); + movePt = new DOMPoint(); + + this.addEventListener('pointermove', pointerMove); + + this.addEventListener( + 'pointerup', + () => this.removeEventListener('pointermove', pointerMove), + { once: true } + ); + } } diff --git a/src/modules/zoom.js b/src/modules/zoom.js index fc0540b..834602b 100644 --- a/src/modules/zoom.js +++ b/src/modules/zoom.js @@ -5,14 +5,17 @@ function zoomIn(deltaY) { } function getScale(e, factor) { - return zoomIn(e.deltaY) ? 1 + factor : 1 - factor; + const outMult = 1 - factor; + const inMult = 1 + factor / outMult + + return zoomIn(e.deltaY) ? inMult : outMult; } 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); + const { x, y, width, height } = el.getBoundingClientRect(); + const pointer = (new DOMPoint(e.clientX, e.clientY)).matrixTransform(inverseScreenCTM); + const origin = (new DOMPoint(x, y)).matrixTransform(inverseScreenCTM); + const terminus = (new DOMPoint(x + width, y + height)).matrixTransform(inverseScreenCTM); return { x: pointer.x, @@ -25,10 +28,10 @@ function getFocalPointBeforeTransform(el, e, inverseScreenCTM) { } 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; + const { x, y, width, height } = el.getBoundingClientRect(); + const origin = (new DOMPoint(x, y)).matrixTransform(inverseScreenCTM); + const terminus = (new DOMPoint(x + width, y + height)).matrixTransform(inverseScreenCTM); + const relativeFocalPoint = fpBeforeTrans.relativeToImageSize; return { x: origin.x + (terminus.x - origin.x) * relativeFocalPoint.x, @@ -37,13 +40,13 @@ function getFocalPointAfterTransform(el, fpBeforeTrans, inverseScreenCTM) { } function getTranslateMatrix(el, e, scaleMatrix) { - const inverseScreenCTM = el.getScreenCTM().inverse(), - fpBeforeTrans = getFocalPointBeforeTransform(el, e, inverseScreenCTM); + const inverseScreenCTM = el.getScreenCTM().inverse(); + const fpBeforeTrans = getFocalPointBeforeTransform(el, e, inverseScreenCTM); el.style.transform = scaleMatrix; - const fpAfterTrans = getFocalPointAfterTransform(el, fpBeforeTrans, inverseScreenCTM), - translateMatrix = new DOMMatrix(); + const fpAfterTrans = getFocalPointAfterTransform(el, fpBeforeTrans, inverseScreenCTM); + const translateMatrix = new DOMMatrix(); return translateMatrix.translate( fpBeforeTrans.x - fpAfterTrans.x, @@ -51,12 +54,14 @@ function getTranslateMatrix(el, e, scaleMatrix) { ); } -export default function (el, e, factor = 0.1) { - e.preventDefault(); +export default function (el, factor = 0.1) { + return e => { + e.preventDefault(); - const mtx = getComputedTransformMatrix(el), - scale = getScale(e, factor), - transMtx = getTranslateMatrix(el, e, mtx.scale(scale)); + const mtx = getComputedTransformMatrix(el); + const scale = getScale(e, factor); + const transMtx = getTranslateMatrix(el, e, mtx.scale(scale)); - el.style.transform = mtx.multiply(transMtx).scale(scale); + el.style.transform = mtx.multiply(transMtx).scale(scale); + } } |