SVG zoom effect is easily achievable using d3js or svgjs libraries, there many code snippets out there how to do it properly. But what about doing this on a raw SVG file without running mentioned libraries? That’s what I was doing in setupmap, processing raw files to implement variety of features.

But, before we start, make sure you are familiar with viewBox and viewPort, and how changing size one of them, affect the other.

This is the original image - red dot in the center of black background:

<?xml version="1.0"?>
<svg baseprofile="tiny" fill="black" width="300" height="300"  version="1.2" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
    <g>
        <rect width="300" height="300" />
        <circle cx="150" cy="150" r="5" fill="red" id="marker" />
    </g>
</svg>


Now, let’s scale image using transform attribute:

<?xml version="1.0"?>
<svg baseprofile="tiny" fill="black" width="300" height="300"  version="1.2" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
    <g transform="scale(1.5)">
        <rect width="300" height="300" />
        <circle cx="150" cy="150" r="5" fill="red" id="marker" />
    </g>
</svg>


The red dot is not in the center of ViewPort anymore, that’s because the ViewBox became “larger” or the relation of ViewPort to ViewBox coordinate system has been changed (scaled in 1.5 times).

In order to return red dot to the center of rectangle we need to shift it, according to different parameters, like viewport/viewbox size, radius of circle, original location of circle, etc. Shifting can be done using translate definition of transform attribute:

<?xml version="1.0"?>
<svg baseprofile="tiny" fill="black" width="300" height="300"  version="1.2" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
    <g transform="scale(1.5) translate(-50, -50)">
        <rect width="300" height="300" />
        <circle cx="150" cy="150" r="5" fill="red" id="marker" />
    </g>
</svg>


So, it was easy, in order to get the zoom effect, you scale and shift using some predefined location. But, how to get the shift values? Here is Python code snippet that is doing just this. The function arguments is self-explanatory and the output is two numbers that must be used in translate definition.

def get_shift(viewport_width, viewport_height, viewbox_width, viewbox_height, x, y, scale):
    viewbox_width /= scale
    viewbox_height /= scale
    width_coof = viewport_width / viewbox_width
    height_coof = viewport_height / viewbox_height
    x_vp = x * width_coof
    y_vp = y * height_coof
    xc = viewport_width / 2
    yc = viewport_height / 2
    sx = 1 if x_vp <= xc else -1
    sy = 1 if y_vp <= yc else -1
    shift_x = sx * abs(x_vp - xc)
    shift_y = sy * abs(y_vp - yc)
    shift_x /= width_coof
    shift_y /= height_coof

    return shift_x, shift_y

Let’s call this function on our sample image:

>> get_shift(300, 300, 300, 300, 150, 150, 1.5)
(-50.0, -50.0)

So, now we can use -50.0, -50.0 in translate definition:

 <g transform="scale(1.5) translate(-50, -50)">