How to Map Pinch to Zoom Gestures to SVG ViewBox

Mapping pinch-to-zoom gestures to an SVG viewBox involves capturing multi-touch screen coordinates, calculating the relative scale and focal center between two fingers, and mathematically updating the viewBox origin and dimensions in real time. By converting client touch coordinates into the SVG coordinate space, you can dynamically adjust the min-x, min-y, width, and height properties to ensure smooth, responsive zooming centered precisely where the user pinches.

The SVG ViewBox Structure

An SVG viewBox attribute consists of four values: min-x, min-y, width, and height. * Decreasing width and height zooms in. * Increasing width and height zooms out. * Adjusting min-x and min-y pans the viewport, keeping the zoom centered under the user’s fingers.

Step 1: Capture Touch Events

Pinch gestures require monitoring two touch points using standard DOM touch events: touchstart, touchmove, and touchend.

let initialDistance = 0;
let initialViewBox = { x: 0, y: 0, w: 1000, h: 1000 };
let focalPoint = { x: 0, y: 0 };

svgElement.addEventListener('touchstart', (e) => {
  if (e.touches.length === 2) {
    e.preventDefault();
    initialDistance = getDistance(e.touches[0], e.touches[1]);
    initialViewBox = getCurrentViewBox();
    focalPoint = getTouchCenterInSvg(e.touches[0], e.touches[1]);
  }
});

Step 2: Calculate Pinch Distance and Focal Point

Use the Pythagorean theorem to calculate the Euclidean distance between the two touches. The center point between the two fingers serves as the focal point of the zoom.

function getDistance(t1, t2) {
  const dx = t1.clientX - t2.clientX;
  const dy = t1.clientY - t2.clientY;
  return Math.hypot(dx, dy);
}

function getTouchCenter(t1, t2) {
  return {
    x: (t1.clientX + t2.clientX) / 2,
    y: (t1.clientY + t2.clientY) / 2
  };
}

Step 3: Convert Screen Coordinates to SVG Space

To ensure the zoom focuses accurately on the user’s touch location regardless of CSS scaling, transform the screen coordinates into SVG coordinate space using the element’s Current Transformation Matrix (getScreenCTM).

function screenToSVG(svg, clientX, clientY) {
  const pt = svg.createSVGPoint();
  pt.x = clientX;
  pt.y = clientY;
  return pt.matrixTransform(svg.getScreenCTM().inverse());
}

Step 4: Calculate and Apply the New ViewBox

During touchmove, determine the scale factor by dividing the current distance between touches by the initial distance. Update the viewBox width and height by this factor, and adjust the origin (x and y) relative to the focal point so the image expands or contracts around the pinch center.

svgElement.addEventListener('touchmove', (e) => {
  if (e.touches.length === 2) {
    e.preventDefault();

    const currentDistance = getDistance(e.touches[0], e.touches[1]);
    const scale = initialDistance / currentDistance;

    // Calculate new dimensions
    const newWidth = initialViewBox.w * scale;
    const newHeight = initialViewBox.h * scale;

    // Adjust origin around the SVG-space focal point
    const newX = focalPoint.x - (focalPoint.x - initialViewBox.x) * scale;
    const newY = focalPoint.y - (focalPoint.y - initialViewBox.y) * scale;

    // Apply the updated viewBox
    svgElement.setAttribute('viewBox', `${newX} ${newY} ${newWidth} ${newHeight}`);
  }
});

Performance and Edge Cases