skip to Main Content

A div contains an SVG (D3 graph) and has its width set to 100%. The size cannot be set to a fixed point because the page should feel responsive.

Expected behaviour: When the user zooms in (ctrl +), the SVG should become bigger, more readable.

Actual behaviour: The SVG shrinks because the surrounding elements (header, navigation etc) take up more space, which means less space for the SVG. Zooming out makes the SVG grow.

What I’ve tried:

/* JS */
document.documentElement.style.setProperty('--zoom-ratio', `${window.devicePixelRatio}`)
/* CSS */
.foo-container {
    overflow: auto;
}
.foo {
    width: calc(var(--zoom-ratio) * 100%);
}

This works, as long as someone uses a screen with a 1:1 pixel ratio. 4k laptop displays are often set to 200-250%, which means that the SVG uses more than twice as much space as it should. How can I fix this?

2

Answers


  1. Chosen as BEST ANSWER

    Sadly the provided answer didn't solve my problem but I found a hack that seems to work well enough. If someone finds a better solution in the future, I will mark that one as the correct answer. Until then, the following worked for me:

      const calcZoomRoughly = (): number => Math.floor(window.outerWidth * 16 * (1 / window.innerWidth)) * (1 / 16);
      const prevRatio = { zoomRatio: calcZoomRoughly() };
      window.addEventListener('resize', () => {
        const currentRatio = calcZoomRoughly();
        if (Math.abs(prevRatio.zoomRatio - currentRatio) < 0.01) return;
        prevRatio.zoomRatio = currentRatio;
        document.documentElement.style.setProperty('--zoom-ratio', `${currentRatio}`);
      });
    

    It uses outerWidth and innerWidth to figure out the ratio. innerWidth is affected by zoom, while outerWidth isn't and system settings affect both, so the ratio stays the same. outerWidth also includes browser borders and drag handles, which means outerWidth / innerWidth is slightly larger than 1.0 if the browser is not in fullscreen mode. So the raw zoom ratio itself is not enough but I round it to an accuracy of 1/16, which seems to work decently well with modern UIs (needs more testing) and hopefully avoids floating point rounding issues better than something like 1/10.

    If the resize event is triggered (and zooming in/out triggers the event) the handler does a quick check if the ratio actually changed meaningfully (again, avoid float errors) and if it did it sets a CSS variable --zoom-ratio that can then be used throughout the project. Don't forget to initialize it to 1.0 in your CSS file.


  2. In my experience its best to have initial value calculated on the BOXSIZE/WindowSize and calculate the Content relative to the box/window
    this will overcome issues of diferent devices accessing the content
    than add listener to change the view on window size change
    Example:

    function adjustSvgSize() {
        const svgContainer = document.querySelector('.foo-container');
        const svgElement = svgContainer.querySelector('svg');
    
        // Assuming your SVG has a viewBox defined like "0 0 width height"
        // You can preserve the aspect ratio while scaling.
        const viewBox = svgElement.viewBox.baseVal;
        const aspectRatio = viewBox.width / viewBox.height;
    
        // Calculate new dimensions based on the container's width
        // and the SVG's aspect ratio
        const newWidth = svgContainer.offsetWidth;
        const newHeight = newWidth / aspectRatio;
    
        // Optionally, adjust the SVG dimensions (if needed)
        svgElement.style.width = `${newWidth}px`;
        svgElement.style.height = `${newHeight}px`;
    }
    
    // Initial adjustment
    adjustSvgSize();
    
    // Adjust on window resize
    window.addEventListener('resize', adjustSvgSize);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search