Utility_Apps/Chrome/Zoom SVG/content.js
2025-09-10 09:31:12 -05:00

136 lines
No EOL
4.1 KiB
JavaScript

function zoomToExtents(svg) {
try {
const bbox = svg.getBBox();
const padding = 10;
const svgClientWidth = svg.clientWidth || 300;
const svgClientHeight = svg.clientHeight || 150;
const viewWidthRaw = bbox.width + 2 * padding;
const viewHeightRaw = bbox.height + 2 * padding;
const aspectBBox = viewWidthRaw / viewHeightRaw;
const aspectViewport = svgClientWidth / svgClientHeight;
let viewWidth = viewWidthRaw;
let viewHeight = viewHeightRaw;
if (aspectBBox > aspectViewport) {
viewHeight = viewWidth / aspectViewport;
} else {
viewWidth = viewHeight * aspectViewport;
}
const cx = bbox.x + bbox.width / 2;
const cy = bbox.y + bbox.height / 2;
const vx = cx - viewWidth / 2;
const vy = cy - viewHeight / 2;
svg.setAttribute("viewBox", `${vx} ${vy} ${viewWidth} ${viewHeight}`);
} catch (e) {
console.warn("zoomToExtents failed", e);
}
}
function screenToSvgCoords(svg, x, y) {
const pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
return pt.matrixTransform(svg.getScreenCTM().inverse());
}
document.querySelectorAll('svg').forEach(svg => {
svg.style.cursor = "zoom-in";
svg.style.outline = "1px dashed #ccc";
// Initial zoom to extents
requestAnimationFrame(() => zoomToExtents(svg));
// Smooth animated zoom on wheel
svg.addEventListener("wheel", function (e) {
e.preventDefault();
const viewBox = svg.getAttribute("viewBox").split(" ").map(Number);
let [vx, vy, vw, vh] = viewBox;
const zoomFactor = 1.3; // smaller step for smoother feel
const zoomIn = e.deltaY < 0;
const scaleTarget = zoomIn ? 1 / zoomFactor : zoomFactor;
const svgPoint = screenToSvgCoords(svg, e.clientX, e.clientY);
const newVW = vw * scaleTarget;
const newVH = vh * scaleTarget;
const newVX = svgPoint.x - ((svgPoint.x - vx) * scaleTarget);
const newVY = svgPoint.y - ((svgPoint.y - vy) * scaleTarget);
let start = null;
const startViewBox = { vx, vy, vw, vh };
const endViewBox = { vx: newVX, vy: newVY, vw: newVW, vh: newVH };
const duration = 10; // ms animation duration
function animate(timestamp) {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / duration, 1);
const interp = (startVal, endVal) =>
startVal + (endVal - startVal) * progress;
const currVX = interp(startViewBox.vx, endViewBox.vx);
const currVY = interp(startViewBox.vy, endViewBox.vy);
const currVW = interp(startViewBox.vw, endViewBox.vw);
const currVH = interp(startViewBox.vh, endViewBox.vh);
svg.setAttribute("viewBox", `${currVX} ${currVY} ${currVW} ${currVH}`);
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}, { passive: false });
// Smooth middle mouse button panning
let isPanning = false;
let startClient = null;
let startViewBox = null;
svg.addEventListener("mousedown", function (e) {
if (e.button !== 1) return; // middle mouse only
e.preventDefault();
isPanning = true;
startClient = { x: e.clientX, y: e.clientY };
startViewBox = svg.getAttribute("viewBox").split(" ").map(Number);
svg.style.cursor = "grabbing";
});
svg.addEventListener("mousemove", function (e) {
if (!isPanning) return;
const dxPixels = e.clientX - startClient.x;
const dyPixels = e.clientY - startClient.y;
const [vx, vy, vw, vh] = startViewBox;
const scaleX = vw / svg.clientWidth;
const scaleY = vh / svg.clientHeight;
const dx = dxPixels * scaleX;
const dy = dyPixels * scaleY;
svg.setAttribute("viewBox", `${vx - dx} ${vy - dy} ${vw} ${vh}`);
});
const stopPan = () => {
isPanning = false;
svg.style.cursor = "zoom-in";
};
svg.addEventListener("mouseup", stopPan);
svg.addEventListener("mouseleave", stopPan);
});