Web Dev Solutions

Catalin Mititiuc

aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCatalin Mititiuc <webdevcat@proton.me>2025-03-18 13:43:26 -0700
committerCatalin Mititiuc <webdevcat@proton.me>2025-03-18 13:47:07 -0700
commit25eca15a3007c76c4e444b859683feb29edfa183 (patch)
treed0a867be54ab95f5e138cec9df662fa0ba450544
parent99d137cc0937c0342fc1076eafe609c8aa370087 (diff)
WIP: translate an object to zoomed position without scalingcm-pan-not-zoom
-rw-r--r--public/index.html30
-rw-r--r--src/app.js25
-rw-r--r--src/modules/pan.js6
-rw-r--r--src/modules/utils.js27
-rw-r--r--src/modules/zoom.js6
5 files changed, 73 insertions, 21 deletions
diff --git a/public/index.html b/public/index.html
index d97baee..bfcf3ef 100644
--- a/public/index.html
+++ b/public/index.html
@@ -7,13 +7,29 @@
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
- <h1>Pan & Zoom SVG Element with CSS/JavaScript</h1>
-
- <p>
- Click and drag the image to pan. Use the mouse wheel to zoom in and out.
- </p>
-
- <object type="image/svg+xml" data="assets/images/image.svg"></object>
+ <!--<object type="image/svg+xml" data="assets/images/image.svg"></object>-->
+ <svg viewBox="-200 -150 400 300">
+ <defs>
+ <circle id="zoom-marker" cx="0" cy="0" r="30" fill="maroon" />
+ <circle id="pan-marker" cx="0" cy="0" r="20" fill="darkgray" />
+ </defs>
+ <g class="pan-zoom">
+ <use id="zm-1" href="#zoom-marker" x="-50" y="-50" />
+ <use id="zm-2" href="#zoom-marker" x="-50" y="50" />
+ <use id="zm-3" href="#zoom-marker" x="50" y="-50" />
+ <use id="zm-4" href="#zoom-marker" x="50" y="50" />
+ </g>
+ <g class="pan">
+ <use id="pm-1" href="#pan-marker" x="-50" y="-50" />
+ <use id="pm-2" href="#pan-marker" x="-50" y="50" />
+ <use id="pm-3" href="#pan-marker" x="50" y="-50" />
+ <use id="pm-4" href="#pan-marker" x="50" y="50" />
+ </g>
+ <g>
+ <!--<circle id="pointer" cx="0" cy="0" r="5" fill="red" stroke="maroon"/>-->
+ <circle cx="0" cy="0" r="1" fill="white" />
+ </g>
+ </svg>
<script src="app.js"></script>
</body>
</html>
diff --git a/src/app.js b/src/app.js
index 9aac0bc..5e9a2e4 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,27 +1,28 @@
import zoom from './modules/zoom';
import pan from './modules/pan';
-const optionalZoomFactor = 0.1,
- object = document.querySelector('object');
+const optionalZoomFactor = 0.1
+ //, 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
// `null`.
window.addEventListener('load', function () {
- const svg = object.contentDocument.querySelector('svg'),
- targetEl = svg.querySelector('g'),
- pointer = svg.querySelector('#pointer'),
+ const svg = document.querySelector('svg'),
+ targetEl = svg.querySelector('g.pan-zoom'),
+ //pointer = svg.querySelector('#pointer'),
options = { passive: false };
svg.addEventListener('wheel', zoom(targetEl, optionalZoomFactor), options);
svg.addEventListener('pointerdown', pan(targetEl), options);
- 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);
- });
+ //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);
+ //});
});
diff --git a/src/modules/pan.js b/src/modules/pan.js
index bf842bd..8be3cb9 100644
--- a/src/modules/pan.js
+++ b/src/modules/pan.js
@@ -1,4 +1,4 @@
-import { default as getComputedTransformMatrix } from './utils';
+import { default as getComputedTransformMatrix, track, getTracked } from './utils';
const minDistanceThreshold = 5;
@@ -41,9 +41,13 @@ function setTransform(el, computedMtx, startPt, endPt) {
const translateMtx = getTranslateMatrix(startPt, endPt);
const transformMtx = computedMtx.multiply(translateMtx);
+ groups.forEach(([z, p]) => track(z, p, transformMtx));
+
el.style.transform = transformMtx;
}
+const groups = getTracked(document.querySelectorAll('svg g[class] use'));
+
export function programmaticPan(el, from, to) {
const matrices = getTransformMatrices(el);
const startPt = clientToSvgPt(from, matrices.inverseScreen);
diff --git a/src/modules/utils.js b/src/modules/utils.js
index e7f5c55..ae63b74 100644
--- a/src/modules/utils.js
+++ b/src/modules/utils.js
@@ -1,5 +1,32 @@
const digits = /-?\d+\.?\d*/g;
+export function extractNum(string) {
+ return string.replace(/\D/g,'');
+}
+
+export function getTracked(trackedAndTrackerEls) {
+ const sorted = [...trackedAndTrackerEls]
+ .sort((a, b) => a.id < b.id || extractNum(a.id) <= extractNum(b.id));
+
+ const groups = [];
+ const els = sorted.slice();
+
+ while (els.length) {
+ groups.push([els.pop(), els.pop()]);
+ }
+
+ return groups;
+}
+
+export function track(targetEl, trackingEl, transformMtx) {
+ let x = targetEl.getAttributeNS(null, 'x');
+ let y = targetEl.getAttributeNS(null, 'y');
+ let ptBefore = new DOMPoint(x, y);
+ let ptAfter = ptBefore.matrixTransform(transformMtx);
+ trackingEl.setAttributeNS(null, 'x', ptAfter.x);
+ trackingEl.setAttributeNS(null, 'y', ptAfter.y);
+}
+
export default function getComputedTransformMatrix(el) {
const matrixSequence = getComputedStyle(el).transform.match(digits),
identityMatrix = '';
diff --git a/src/modules/zoom.js b/src/modules/zoom.js
index 1455cb4..69652c0 100644
--- a/src/modules/zoom.js
+++ b/src/modules/zoom.js
@@ -1,4 +1,4 @@
-import { default as getComputedTransformMatrix } from './utils';
+import { default as getComputedTransformMatrix, extractNum, track, getTracked } from './utils';
function zoomIn(deltaY) {
return deltaY < 0;
@@ -24,9 +24,13 @@ function setTransform(el, computedMtx, translateMtx, scale) {
const transformMtx =
computedMtx.multiply(translateMtx).scale(scale).multiply(translateMtx.inverse());
+ groups.forEach(([z, p]) => track(z, p, transformMtx));
+
el.style.transform = transformMtx;
}
+const groups = getTracked(document.querySelectorAll('svg g[class] use'));
+
export default function (el, factor = 0.1) {
return e => {
e.preventDefault();