DEV Community

Cover image for JavaScript: Zoom like in maps for SVG/HTML
Alexey Boyko
Alexey Boyko

Posted on

JavaScript: Zoom like in maps for SVG/HTML

Fig 1. Zoom in dgrm.net flowchart editor Fig 1. Zoom in dgrm.net flowchart editor

dgrm.net | GitHub

How zoom is done in the dgrm.net flowchart editor.
You can zooming with

  • mouse wheel,
  • touchpad
  • and two fingers on phones and tablets.

A ready-made SVG zoom function for your projects is included.

Zoom algorithm

SVG and HTML have scaling. Scaling in HTML.

Just changing the scale is not enough. The image will slide away.
When zoomed in, the circle in the center of the screen slides down to the right.

Fig 2. Wrong: when zoomed in, the circle in the center of the screen slides down to the right. Fig 2. Wrong: when zoomed in, the circle in the center of the screen slides down to the right.

Fig 3. When zoomed in, the circle in the center of the screen slides down to the right — scheme. Fig 3. When zoomed in, the circle in the center of the screen slides down to the right — scheme.

The black rectangle is the screen. Blue is an enlarged image.
The enlarged circle is in the center of the enlarged image, but has slid away relative to the screen.

It is necessary to scale and shift, then the center of the image will not leave.

Fig 4. Zoom in and shift up to the left. The circle remains in the center of the screen. Fig 4. Zoom in and shift up to the left. The circle remains in the center of the screen.

In figure 4, the center of the image does not slide away.
But that’s not how maps work. Maps are not zooming to the center. The maps are zoomed in relative to the cursor. The place where the cursor points does not move relative to the screen.

The building in the center of the map goes down. It is highlighted in red. The building under the cursor remains in place. Highlighted in blue.

Fig 5. The map is not zooming to the center, but relative to the cursor. The place where the cursor points does not move relative to the screen. Fig 5. The map is not zooming to the center, but relative to the cursor. The place where the cursor points does not move relative to the screen.

The zoom function should move the image so that the point under the cursor remains in place.

/**
 * @param {SVGGraphicsElement} svgEl
 * @param {Point} fixedPoint
 *                this point will not change position while scale
 * @param {number} scale
 * @param {number} nextScale
 */
export function svgScale(svgEl, fixedPoint, scale, nextScale) {
    const position = svgPositionGet(svgEl);

    svgPositionSet(svgEl, {
        x: nextScale / scale * (position.x - fixedPoint.x) +     
           fixedPoint.x,
        y: nextScale / scale * (position.y - fixedPoint.y) + 
           fixedPoint.y
    });

    ensureTransform(svgEl, SVGTransform.SVG_TRANSFORM_SCALE)
        .setScale(nextScale, nextScale);
}
Enter fullscreen mode Exit fullscreen mode

Listing 1. Zoom function. Shifts the image so that the fixedPoint stays in place.
Helper functions svgPositionGet, svgPositionSet, ensureTransform are available on GitHub.

Zoom with mouse wheel and touchpad

Subscribe to the mouse wheel event “wheel”. There is no separate event for pinching with two fingers on the touchpad. The pinch uses the same “wheel” event.

For the wheel, the scale changes in increments of 0.25, and for the touchpad 0.05. The values are chosen so that:

  • the mouse wheel did not need to be twisted for a long time
  • and on the touchpad the image did not jump
// 'svg' is type of {SVGSVGElement}
let scale = 1;
// mouse wheel, trackpad pitch
svg.addEventListener('wheel', /** @param {WheelEvent} evt */ evt => {
    evt.preventDefault();


    // calc nextScale

    const delta = evt.deltaY || evt.deltaX;
    const scaleStep = Math.abs(delta) < 50
        ? 0.05  // touchpad pitch
        : 0.25; // mouse wheel

    const scaleDelta = delta < 0 ? scaleStep : -scaleStep;
    const nextScale = scale + scaleDelta; // 'scale' is prev scale


    // calc fixedPoint
    const fixedPoint = { x: evt.clientX, y: evt.clientY };


    // scale
    // 'svgEl' is element to scale
    svgScale(svgEl, fixedPoint, scale, nextScale);
    scale = nextScale;
});
Enter fullscreen mode Exit fullscreen mode

Listing 2. Subscribe to the mouse wheel event. A touchpad pinch fires the same event. See the full code on GitHub.

Zoom with two fingers on phones and tablets

For finger zoom, the fixed point is the midway point between the fingers. The change in scale depends on the change in the distance between the fingers.

You also need to consider that the image can be zoomed and moved at the same time.

// calc nextScale

// distance between fingers
const distanceNew = Math.hypot(
    firstFinger.x - secondFinger.x,
    firstFinger.y - secondFinger.y);

// 'distance' is previous distance between fingers
// 'scale' is previous scale
const nextScale = scale / distance * distanceNew;


// calc fixedPoint
const fixedPoint = {
    x: (firstFinger.x + secondFinger.x) / 2,
    y: (firstFinger.y + secondFinger.y) / 2
};


// scale
// 'svgEl' is element to scale
svgScale(svgEl, fixedPoint, scale, nextScale);
scale = nextScale;

// don't forget to also move the canvas behind your fingers
Enter fullscreen mode Exit fullscreen mode

Listing 3. For finger zoom, the fixed point is the midway point between the fingers. The change in scale depends on the change in the distance between the fingers. See the full code on GitHub.

Other articles about dgrm.net

How to support the project

  • Start using the flowchart editor dgrm.net. Tell me what you think. Comments, private messages, on GitHub. I read everything, I keep a list of proposals.
  • Tell your friends.
  • Give a star on GitHub.

Top comments (0)