Conjugate scaling operation by translating to the origin
https://stackoverflow.com/questions/38446666/scaling-around-a-specific-point-in-2d-coordinate-system
This commit is contained in:
parent
3fa7f02571
commit
d2a670731f
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pan-zoom",
|
"name": "pan-zoom",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pan-zoom",
|
"name": "pan-zoom",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.20.2",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pan-zoom",
|
"name": "pan-zoom",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"description": "Pan/zoom SVG images in the browser",
|
"description": "Pan/zoom SVG images in the browser",
|
||||||
"browser": "index.js",
|
"browser": "index.js",
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import getComputedTransformMatrix from './utils.js';
|
import { default as getComputedTransformMatrix } from './utils';
|
||||||
|
|
||||||
const minDistanceThreshold = 5;
|
const minDistanceThreshold = 5;
|
||||||
|
|
||||||
@ -31,23 +31,26 @@ function getTransformMatrices(el) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function clientToSvgPt({ clientX, clientY }, { inverseScreen }, pt = new DOMPoint()) {
|
function clientToSvgPt(e, inverseScreenMtx, pt = new DOMPoint()) {
|
||||||
pt.x = clientX;
|
pt.x = e.clientX;
|
||||||
pt.y = clientY;
|
pt.y = e.clientY;
|
||||||
return pt.matrixTransform(inverseScreen);
|
return pt.matrixTransform(inverseScreenMtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPanTransform(el, { computed }, startPt, endPt) {
|
function setTransform(el, computedMtx, startPt, endPt) {
|
||||||
el.style.transform = computed.multiply(getTranslateMatrix(startPt, endPt));
|
const translateMtx = getTranslateMatrix(startPt, endPt);
|
||||||
|
const transformMtx = computedMtx.multiply(translateMtx);
|
||||||
|
|
||||||
|
el.style.transform = transformMtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function programmaticPan(el, from, to) {
|
export function programmaticPan(el, from, to) {
|
||||||
const matrices = getTransformMatrices(el);
|
const matrices = getTransformMatrices(el);
|
||||||
const startPt = clientToSvgPt(from, matrices);
|
const startPt = clientToSvgPt(from, matrices.inverseScreen);
|
||||||
const endPt = clientToSvgPt(to, matrices);
|
const endPt = clientToSvgPt(to, matrices.inverseScreen);
|
||||||
|
|
||||||
el.style.transition = 'transform 0.5s';
|
el.style.transition = 'transform 0.5s';
|
||||||
setPanTransform(el, matrices, startPt, endPt);
|
setTransform(el, matrices.computed, startPt, endPt);
|
||||||
el.addEventListener('transitionend', () => el.style.transition = '', { once: true });
|
el.addEventListener('transitionend', () => el.style.transition = '', { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,13 +63,13 @@ export default function (el) {
|
|||||||
|
|
||||||
if (!isPanning && exceedsMinDistanceThreshhold(startPt, movePt)) {
|
if (!isPanning && exceedsMinDistanceThreshhold(startPt, movePt)) {
|
||||||
isPanning = true;
|
isPanning = true;
|
||||||
startPt = clientToSvgPt(e, matrices, startPt);
|
startPt = clientToSvgPt(e, matrices.inverseScreen, startPt);
|
||||||
stopEventPropagationToChildren(el, 'click');
|
stopEventPropagationToChildren(el, 'click');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPanning) {
|
if (isPanning) {
|
||||||
movePt = clientToSvgPt(e, matrices, movePt);
|
movePt = clientToSvgPt(e, matrices.inverseScreen, movePt);
|
||||||
setPanTransform(el, matrices, startPt, movePt);
|
setTransform(el, matrices.computed, startPt, movePt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,67 +1,40 @@
|
|||||||
import getComputedTransformMatrix from './utils.js';
|
import { default as getComputedTransformMatrix } from './utils';
|
||||||
|
|
||||||
function zoomIn(deltaY) {
|
function zoomIn(deltaY) {
|
||||||
return deltaY < 0;
|
return deltaY < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScale(e, factor) {
|
function getScale(deltaY, factor) {
|
||||||
const outMult = 1 - factor;
|
const outMult = 1 - factor;
|
||||||
const inMult = 1 + factor / outMult
|
const inMult = 1 + factor / outMult
|
||||||
|
|
||||||
return zoomIn(e.deltaY) ? inMult : outMult;
|
return zoomIn(deltaY) ? inMult : outMult;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFocalPointBeforeTransform(el, e, inverseScreenCTM) {
|
function getTranslateMatrix(el, clientX, clientY) {
|
||||||
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,
|
|
||||||
y: pointer.y,
|
|
||||||
relativeToImageSize: {
|
|
||||||
x: (pointer.x - origin.x) / (terminus.x - origin.x),
|
|
||||||
y: (pointer.y - origin.y) / (terminus.y - origin.y)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFocalPointAfterTransform(el, fpBeforeTrans, inverseScreenCTM) {
|
|
||||||
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,
|
|
||||||
y: origin.y + (terminus.y - origin.y) * relativeFocalPoint.y
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTranslateMatrix(el, e, scaleMatrix) {
|
|
||||||
const inverseScreenCTM = el.getScreenCTM().inverse();
|
const inverseScreenCTM = el.getScreenCTM().inverse();
|
||||||
const fpBeforeTrans = getFocalPointBeforeTransform(el, e, inverseScreenCTM);
|
const translateMtx = new DOMMatrix();
|
||||||
|
const pointer = new DOMPoint(clientX, clientY)
|
||||||
|
const { x, y } = pointer.matrixTransform(inverseScreenCTM);
|
||||||
|
|
||||||
el.style.transform = scaleMatrix;
|
return translateMtx.translate(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
const fpAfterTrans = getFocalPointAfterTransform(el, fpBeforeTrans, inverseScreenCTM);
|
function setTransform(el, computedMtx, translateMtx, scale) {
|
||||||
const translateMatrix = new DOMMatrix();
|
const transformMtx =
|
||||||
|
computedMtx.multiply(translateMtx).scale(scale).multiply(translateMtx.inverse());
|
||||||
|
|
||||||
return translateMatrix.translate(
|
el.style.transform = transformMtx;
|
||||||
fpBeforeTrans.x - fpAfterTrans.x,
|
|
||||||
fpBeforeTrans.y - fpAfterTrans.y
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (el, factor = 0.1) {
|
export default function (el, factor = 0.1) {
|
||||||
return e => {
|
return e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const mtx = getComputedTransformMatrix(el);
|
const computedMtx = getComputedTransformMatrix(el);
|
||||||
const scale = getScale(e, factor);
|
const scale = getScale(e.deltaY, factor);
|
||||||
const transMtx = getTranslateMatrix(el, e, mtx.scale(scale));
|
const translateMtx = getTranslateMatrix(el, e.clientX, e.clientY);
|
||||||
|
|
||||||
el.style.transform = mtx.multiply(transMtx).scale(scale);
|
setTransform(el, computedMtx, translateMtx, scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user