Search code examples
javascriptsvgpositionviewbox

Get the effective viewbox of an <svg> element?


An <svg> element is allowed to have a viewBox and a preserveAspectRatio attribute, which together determine the actual viewbox that is displayed on screen.

The viewBox attribute specifies a rectangle in SVG coordinates, which implicitly specifies an aspect ratio. This aspect ratio may not in general be the same as that of the SVG element. The preserveAspectRatio attribute specifies whether to adjust the zoom so that the entire specified viewbox fits into the SVG element or adjust the zoom so that the entire SVG element fits into the specified viewbox, as well as the alignment when performing this fitting.

From JavaScript, I would like to convert a mouse position (offsetX/offsetY) to SVG coordinates. This necessitates finding out the transformation from screen/offset coordinates to SVG coordinates, which depends on the SVG viewbox. Instead of doing math to calculate the actual SVG viewbox (with the same aspect ratio as the SVG element) manually from the viewBox and preserveAspectRatio attributes, does SVG expose any interface that I can simply call to get the computed viewbox?


Solution

  • I guess what you need is a DOMPoint and its relative position according to the SVGs DOMMatrix. That is as fare as I can explain it.

    So, here an example where the SVG is squeezed in height and you can see that the point where you click matches the viewBox size.

    const svg = document.getElementById('svg01');
    const print = document.getElementById('print');
    
    const toSVGPoint = (svg, x, y) => {
      let p = new DOMPoint(x, y);
      return p.matrixTransform(svg.getScreenCTM().inverse());
    };
    
    svg.addEventListener('click', e => {
      let p = toSVGPoint(e.target, e.clientX, e.clientY);
      print.textContent = `x: ${p.x} - y: ${p.y}`;
    });
    svg {
      border: thin solid black;
      cursor: pointer;
    }
    <p id="print">Position</p>
    <svg id="svg01" xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 1000 500" preserveAspectRatio="none"
      width="400" height="150">
      <text font-size="100" x="50%" y="50%" dominant-baseline="middle"
      text-anchor="middle" pointer-event="none">Click me...</text>
    </svg>