Search code examples
javascripthtmlcsssvgpanzoom

How to zoom and scroll an <svg> element


I'm a newbie in html/css/JS development, I'm trying to learn for hobbyist purpose. I'm trying to create a page with an <svg> element inside a <div> and I'm looking for a way to pan with scrollbar and zoom its content without using external libraries (because I'd like to understand the mechanism behind first).

Here below the code of my page with a couple of buttons to zoom in and out a blue square drawn with a <rect> element. More or less it works but when I'm zooming in and scroll bars appear, when I drag them I'm not able to see completely the square, it looks clipped out if the viewport.

I also tried to play with the viewBox attribute in the but probably I didn't fully got how it works, maybe that's the right way to go....

How could I modify this code to obtain the result I'm looking for in a robust and elegant way?

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0px;
            padding: 0px;
        }

        #container {
            width: 80lvw;
            height: 80lvh;
            border: 1px solid black;
            justify-content:left;
            overflow: auto;
            --scale-k: 1;
        }

        #svg {
            transform: scale(var(--scale-k));
            transform-origin: center;
        }
    </style>

    <script>

    </script>
</head>

<body>
    <div id="container">
        <svg id="svg" xmlns="http://www.w3.org/2000/svg" version="1.1" height="100%" width="100%" stroke="black"
            stroke-width="3" fill="transparent">
            <rect x="100" y="100" width="100" height="100" fill="blue" rx="20" ry="20" />
        </svg>
    </div>
    <button type="button" id="zoom-in" style="z-index: 100000">zoom in</button>
    <button type="button" id="zoom-out" style="z-index: 100000">zoom out</button>
</body>

<script>

    const container = document.querySelector('#container');
    const svg = document.querySelector('#svg');

    let zoomF = window.getComputedStyle(container).getPropertyValue('--scale-k');
    console.log(zoomF);

    const btnZoomIn = document.querySelector('#zoom-in');
    const btnZoomOut = document.querySelector('#zoom-out');

    btnZoomIn.addEventListener('click', (evt) => {
        zoomF *= 1.1;
        resize();
    });

    btnZoomOut.addEventListener('click', (evt) => {
        zoomF /= 1.1;
        resize();
    });

    function resize() {
        let svgWidth = parseInt(svg.getAttribute('width'));
        svg.setAttribute('width', `${(svgWidth * zoomF)}%`);
        let svgHeight = parseInt(svg.getAttribute('height'));
        svg.setAttribute('height', `${(svgHeight * zoomF)}%`);
        container.style.setProperty('--scale-k', zoomF);
    }


</script>

</html>


Solution

  • Here's my take on it. There are quite a few changes so take a moment to compare to your version.

    <!DOCTYPE html>
    <html lang="en">
       <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <style>
             * {
               margin: 0px;
               padding: 0px;
             }
             #container {
               width: 80lvw;
               height: 80lvh;
               border: 1px solid black;
               overflow: auto;
             }
             #svg {
               margin: 50px;
             }
          </style>
       </head>
       <body>
          <div id="container">
             <svg width="100" height="100" id="svg" xmlns="http://www.w3.org/2000/svg" version="1.1" 
                fill="transparent">
                <rect stroke-width="3" stroke="black" width="100%" height="100%" fill="blue" rx="20" />
             </svg>
          </div>
          <button id="zoom-in">zoom in</button>
          <button id="zoom-out">zoom out</button>
       </body>
       <script>
          const svg = document.querySelector('#svg');
          
          const btnZoomIn = document.querySelector('#zoom-in');
          const btnZoomOut = document.querySelector('#zoom-out');
          
          btnZoomIn.addEventListener('click', () => {
              resize(1.1);
          });
          
          btnZoomOut.addEventListener('click', () => {
              resize(0.9);
          });
          
          function resize(scale) {
              let svgWidth = parseInt(svg.getAttribute('width'));
              svg.setAttribute('width', `${(svgWidth * scale)}`);
              let svgHeight = parseInt(svg.getAttribute('height'));
              svg.setAttribute('height', `${(svgHeight * scale)}`);
          }
       </script>
    </html>
    

    I notice that this solution has a little render issue with the border radius but I'm only concerned with the zooming for now.