Home > OS >  Scaling SVG with cursor position as transform origin
Scaling SVG with cursor position as transform origin

Time:12-07

I am trying to scale an SVG cirle with trackpad (by moving two fingers up/down) and the origin of the transform must be the position of the cursor. The scaling performs well for the first time, and for every other attempt the circle changes position (it should not). This can be seen clearly if the cursor is inside the circle and near the perimeter of it. Below is the code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" content="width=device-width">

    <style>
        .container
        {
            position: fixed;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
        }

    </style>
</head>
<body>


<div class="container">
    <svg id="svg" height="600" width="600">
        <circle cx="300" cy="300" r="300" stroke="black" stroke-width="3" fill="white"/>
    </svg>
</div>

<script>
    let scale = 1;
    const e = document.getElementById("svg");

    function wheelZoom(event)
    {
        event.preventDefault();

        scale  = event.deltaY * -0.01;
        scale = Math.min(Math.max(.5, scale), 2);

        x = 100*(event.clientX-e.getBoundingClientRect().x)/e.getBoundingClientRect().width;
        y = 100*(event.clientY-e.getBoundingClientRect().y)/e.getBoundingClientRect().height;

        e.style.transformOrigin = `${x}% ${y}%`;
        e.style.transform = `scale(${scale})`;
    }

    e.addEventListener("wheel", wheelZoom);
</script>


</body>
</html>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

I'm not sure about a couple of things:

  • what you mean by you don't want the circle to change position
  • also whether you actually want the whole SVG to scale, or just the circle inside the SVG

In the following demo, I went with keeping the SVG unchanged, but scaling the circle based on where inside the SVG you are when you roll the mouse wheel

Hopefully it is what you were after.

//    let scale = 1;
const svg = document.getElementById("svg");
const circle = document.querySelector("svg circle");

// Circle transform. Inits to 1:1 scale (called an "identity transform").
var   circleTransform = svg.createSVGMatrix();  // start

svg.addEventListener("wheel", wheelZoom);


function wheelZoom(event)
{
   event.preventDefault();

   // Get the mouse position as SVG coordinates
   var coords = convertScreenCoordsToSvgCoords(event.clientX, event.clientY);

   // Calculate an appropriate scale adjustment
   var scale = 1.0   (event.deltaY * 0.001);

   // To scale around the mouse coords, first we transform the coordinate
   // system so that the origin is at the mouse coords.
   circleTransform = circleTransform.translate(coords.x, coords.y);
   // Then we apply the scale
   circleTransform = circleTransform.scale(scale, scale);
   // Finally we move the coordinate system back to where it was
   circleTransform = circleTransform.translate(-coords.x, -coords.y);

   // Now we need to update the circle's transform
   var transform = svg.createSVGTransform();        // An SVGTransform DOM object...
   transform.setMatrix(circleTransform);            // set to the new circleTransform...
   circle.transform.baseVal.initialize(transform);  // and used to update the circle transform property
}


function convertScreenCoordsToSvgCoords(x, y) {
   var pt = svg.createSVGPoint();  // An SVGPoint SVG DOM object
   pt.x = x;
   pt.y = y;
   // getScreenCTM tells us the combined transform that determines where 
   // the circle is rendered. Including any viewBox.
   // We use the inverse of that to convert the mouse X and Y to their
   // equivalent values inside the SVG.
   pt = pt.matrixTransform(circle.getScreenCTM().inverse());
   return {'x': pt.x, 'y': pt.y};
}
svg {
  background-color: linen;
}
<div class="container">
   <svg id="svg" height="600" width="600">
      <circle cx="300" cy="300" r="300" stroke="black" stroke-width="3" fill="white"/>
   </svg>
</div>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related