Search code examples
mathsvgslidercoordinateszooming

Calculating svg transform values for zooming-in and dragging an image


We have available svg area of 1300x620px, wanting to vertically and horizontally center 300x500px image with ability to zoom in and out, with default zoom level of 3.

We start by calculating the image size by dividing the available area size by the default zoom level, ending with 433x207px image, which we wrap in a <g> tag with transform="translate(0,0) scale(3)" and the result looks like that:enter image description here

The question is, how to calculate the proper x and y here translate(x, y) scale(3.5) so that the image is still centered.

The second question is, how to calculate minimum and maximum x,y coordinates of the image.

The idea is to implement sliders for the x and y axis and 2 buttons to zoom-in / zoom-out which will modify the transform() values for the slider functionality, but we need to know the min-max values on each axis as well as to properly transform the image so that it keeps to be centered no matter of the current transform applied.


Solution

  • I'll list the principal calculation in pseudocode first, and than demonstrate its functioning graphically. Finally we'll sum up things.

    Calculus

    So according to your description we start with:

    // SVG viewport size
    set viewWidth = 1300px;
    set viewHeight = 620px;
    
    // initial scale factor and according image size
    set initScale = 3;
    set imgWidth = viewWidth / initScale;
    set imgHeight = viewHeight / initScale;
    

    To adjust the position of your image we have to apply a factor to the image size:

    set newScale = 3.5;
    set adjustFactor = 0.5 * (1 - newScale / initScale);
    scale(newScale);
    translate(adjustFactor * imgWidth, adjustFactor * imgHeight);
    

    When zooming in, e.g. with newScale = 3.5, we get (rounded):

    scale(3.5);
    translate(-36, -17);
    

    Now lets zoom out (rounded again):

    scale(2.5);
    translate(36, 17);
    

    That behavior is to be expected. To center we have to left-/up-shift when zooming in, and right-/down-shift when zooming out.

    Scheming

    Fig.1 illustrates what is done here. We have a grayisch square within a 10 x 6 px "canvas". We have a frame of the same size, which remains unchanged and shall always show the square centered, no matter how we scale the canvas (together with the square). Now, when scaling up the canvas without an adjusting translation, the canvas slips out of the frame, and we see only the top-left corner of the square. Using the above formula and the resulting adjustment factor of 0.5 we push the canvas back by -5px along the x-axis and -3px along the y-axis till the square is centered within the frame again.

    adjustment Fig.1 Adjusting the position of an image slippen out of the frame by scaling, to be shown centered in that frame.

    The coordinates are correspondingly:

    minX = adjustFactor * imgWidth;
    maxX = minX + imgWidth * (newScale / initScale);
    
    minY = adjustFactor * imgHeight;
    maxY = minY + imgHeight * (newScale / initScale);
    

    The max-Values are basically min-Value plus newly scaled width resp. height.

    Crunching

    Note that

    maxX = minX + imgWidth * (newScale / initScale)
         = adjustFactor * imgWidth + imgWidth * (newScale / initScale)
         = imgWidth * (adjustFactor + newScale / initScale)
         = imgWidth * (0.5 * (1 - newScale / initScale) + newScale / initScale)
         = imgWidth * (0.5 - 0.5 * newScale / initScale + newScale / initScale)
         = imgWidth * (0.5 + 0.5 * newScale / initScale)
         = imgWidth * 0.5 * (1 + newScale / initScale)
    

    Remembers of something, doesn't it?

    adjustFactor = 0.5 * (1 - newScale / initScale);
    

    It's just a plus instead of a minus. The difference between 0.5 * (1 + newScale / initScale) and 0.5 * (1 - newScale / initScale) is newScale / initScale. We may want to consider this, as divisions are costly concerning performance. The less the better.

    Mopping Up

    So in total we get:

    set a = 0.5 * (1 - newScale / initScale); // our former "adjustFactor"
    set b = a + newScale / initScale;
    
    set minX = a * imgWidth;
    set maxX = b * imgWidth;
    
    set minY = a * imgHeight;
    set maxY = b * imgHeight;
    
    scale(newScale);
    translate(minX, minY);  // looks sane, right?
    

    You can use the min/max-values for scrolling checks. It might make sense to turn off scrolling for an axis when the respective min-value is >= 0.