Search code examples
canvasflexboxresize-observer

A strange interaction between canvas, ResizeObserver and FlexBox


I am trying to get a self-resizing canvas following the recipe here (end of section 4, under "What to do instead"). So I have a ResizeObserver hooked up to watch for changes on my canvas element. The canvas element has 100% height and is inside of a container element which is itself a child of a flex element. This exact set of circumstances seems to lead to a chain reaction in which the canvas's height continually increases over time.

The reason I want this is because I want the canvas to fill its container, hence the 100% height, but I don't want its actual rendered content to be stretched by the browser.

Here's a JSFiddle and here's a minimum example in code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body
        {
            display: flex;
        }

        main
        {
            max-height: 1000px; /* not necessary, for convenience */
        }

        canvas
        {
            height: 100%;
            background-color: pink;
        }
    </style>
</head>
<body>
    <main>
        <canvas></canvas>
    </main>

    <script>
        let canvas = document.querySelector("canvas");
        let renderer = canvas.getContext("2d");

        let resizeObserver = new ResizeObserver(render);
        resizeObserver.observe(canvas);

        function render()
        {
            renderer.canvas.height = renderer.canvas.clientHeight;
        }
    </script>
</body>
</html>

Solution

  • This is because your canvas has its display set to inline-block, it thus is affected by the line-height value of the parent <main> and will take more space in this parent than it's actual size.

    To avoid that, either set the <main>'s line-height to 0,

    let canvas = document.querySelector("canvas");
    let renderer = canvas.getContext("2d");
    
    let resizeObserver = new ResizeObserver(resize);
    resizeObserver.observe(canvas, { box: "content-box"});
    i = 0;
    function resize()
    {
        canvas.height = canvas.clientHeight;
    }
    body
    {
      display: flex;
    }
    
    main
    {
      line-height: 0;
    }
    
    canvas
    {
      height: 100%;
      background-color: pink;
    }
    <main>
        <canvas></canvas>
    </main>

    Or set the <canvas> display to block.

    let canvas = document.querySelector("canvas");
    let renderer = canvas.getContext("2d");
    
    let resizeObserver = new ResizeObserver(resize);
    resizeObserver.observe(canvas, { box: "content-box"});
    i = 0;
    function resize()
    {
        canvas.height = canvas.clientHeight;
    }
    body
    {
      display: flex;
    }
    
    canvas
    {
      height: 100%;
      background-color: pink;
      display: block;
    }
    <main>
        <canvas></canvas>
    </main>