javascriptcanvas

Resize canvas element when parent changes its width/height


In my application I have a div with a canvas element inside it that display an image. The application also has a sidebar that can be hidden by clicking a button, and all the other elements should resize themselves to take the empty remaining space.

This works well for divs and other elements that have a width/height set to percents, but not for the canvas element.

This is the div I have:

<div id="background-img" class="image-div"></div>

.image-div {
  height: inherit;
  width: 100%;
  margin-top: 25px;
  position: absolute;
}

where the height is initially 305px but it changes to 375px when the sidebar is hidden.

This is the canvas creation code:

const imgDiv = document.getElementById('background-img');
if (imgDiv) {
  const blob = new Blob([svg], { type: 'image/svg+xml' });
  const url = URL.createObjectURL(blob);

  const canvas = document.createElement('canvas');
  canvas.className = 'canvas-background';
  canvas.id = 'canvas-id';
  const ctx = canvas.getContext('2d');

  canvas.width = imgDiv.clientWidth;
  canvas.height = imgDiv.clientHeight;

  const img = new Image();
  img.onload = function() {
    ctx.drawImage(img, 0, 0);
  };
  img.src = url;
  imgDiv.appendChild(canvas);
}

and the canvas has no additional css defined anywhere.

How can I make it possible for the canvas to resize itself as soon as the parent changes his width and height (I am trying to make this resize work also when I zoom in/out the page like you do it in chrome for example, something that works with divs or img elements).


Solution

  • The problem is that there is not (yet) a good way to do this. Instinctively you would look for a resize event on an element. But that can only be applied to the window as explained here on MDN.

    After doing some research and looking at other threads I found a couple of options for you to consider.

    Continuous loop

    Draw the image in a recursive function which will also check the imgDiv's width and height every time it draws itself.

    CSS:

    canvas {
        width: 100%;
        height: 100%;
    }
    

    JS:

    function draw(ctx, imgDiv, img) {
        ctx.canvas.width = imgDiv.offsetWidth;
        ctx.canvas.height = imgDiv.offsetheight;
        ctx.drawImage(img, 0, 0);
        requestAnimationFrame(() => {
            draw(ctx, imgDiv, img);
        });
    }
    
    img.onload = function() {
        draw(ctx, imgDiv, img);
    };
    

    This option has the benefit of checking all the time it can, but I image it can be quite heavy in terms of performance. requestAnimationFrame does improve performance by firing the function only when it is allowed in the speed of the framerate. So about 60 times per second on average.

    ResizeObserver (Warning: experimental technology)

    This is one I have not seen anywhere yet. The ResizeObserver is new and is in the family of the IntersectionObserver, PerformanceObserver and MutationObserver. Like the name suggests it observers changes in elements that are resized and fires a callback when it detects a change.

    It works like this:

    const observer = new ResizeObserver(entries => {
        entries.forEach(entry => {
            // Update the canvas dimensions. 
        });
    });
    
    // Observer the image-div when it resizes.
    observer.observe(imgDiv);
    

    Since it is experimental I wouldn't recommend using it, although it would perfectly fit your scenario. More on browser support for this API her on Caniuse and an article on how to use it from Alligator.io.

    Use the resize event on window

    This would only capture the scenario of changing the width of the window or document. And in case of your sidebar opening and closing it would need some extra tinkering.

    window.addEventListener('resize', (event) => {
        requestAnimationFrame(() => {
            // Update the canvas dimensions. 
        });
    });
    

    Using requestAnimationFrame here is recommended since the resize event will be called on every pixel and can slow down performance.

    Edit:

    It seems that Artyomska solved the problem already. And although the issue is resolved I still want to share my research in case a future query may stumble on this thread.