alexk745
alexk745

Reputation: 38

Canvas grows in size when fitting to flex container size

The layout I want to achieve is (No need to read, JSFiddle is self explanatory. Try resizing):

I have 2 problems:

  1. Resizing the window makes the canvas grow in size infinitely. (Try resizing the JSFiddle frame)
  2. The content has overflown vertically, I am guessing from the original resize call.

I tried to make the example as simple as possible and commented most of the code, hope it is enough.

test.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>Test</title>
        <link rel="stylesheet" href="normalize.css">
        <link rel="stylesheet" href="test.css">
        <script src="test.js"></script>
    </head>
    <body>
        <div id="content">
            <div id="content-header">HEADER</div>
            <div id="panels">
                <div class="canvas-wrapper">
                    <canvas id="test-canvas"></canvas>
                </div>
            </div>
        </div>
    </body>
</html>

test.css

/* Border box */

html {
    -webkit-box-sizing: border-box;
       -moz-box-sizing: border-box;
            box-sizing: border-box;
}

*,
*::before,
*::after {
    -webkit-box-sizing: inherit;
        -moz-box-sizing: inherit;
            box-sizing: inherit;
}

/* Full height #content */

html, body, #content {
    height: 100%;
}

/* #panels fills verical space */

#content {
    display: flex;
    flex-direction: column;
}

#content-header {
    flex: 0 1 0;
}

#panels {
    flex: 1 0 0;
}

/* #panels is flex row container */

#panels {
    display: flex;
    flex-direction: row;
}

/* Wrapping canvas to calculate size from flex container */

.canvas-wrapper {
    display: block;

    /* Can grow and can shrink */
    flex: 1 1 auto;

    background-color: rgba(0, 0, 255, 0.2);
}

canvas {
    display: block;
    
    /* Center horizontally */
    margin: 0 auto;

    border: 1px solid red;
}

test.js

// width X height -> size X size, size is min(parentWidth, parentHeight)
function fitToParent(element) {
    // parent element is the .canvas-wrapper
    const parentWidth = element.parentElement.clientWidth;
    const parentHeight = element.parentElement.clientHeight;

    const size = Math.min(parentWidth, parentHeight);

    // for debug
    console.log('Wrapper size = ' + parentWidth + 'x' + parentHeight + ' Canvas size = ' + size + 'x' + size);

    element.width = size;
    element.height = size;
}

window.addEventListener('load', function() {
    const canvas = document.getElementById('test-canvas');
    const ctx = canvas.getContext('2d');

    function resize() {
        fitToParent(canvas);

        // random draw operation, doesn't matter
        ctx.fillStyle = 'green';
        ctx.fillRect(10, 10, 150, 100);
    }

    window.addEventListener('resize', resize);

    resize();
});

JSFiddle here

Upvotes: 2

Views: 1620

Answers (1)

alexk745
alexk745

Reputation: 38

From some more testing, it seems that the problem occurs due to the #panels flex container(or any other display style really) treating the size of the canvas as the minimum.

A workaround I found is to first set the canvas width and height to 0 before resizing. But I am not sure if that should be ok, it causes some jerky animations which I would like to avoid. I will wait before accepting this answer in case anyone can think of something else.

Also, the second problem (the vertical overflow) still exists.

// width X height -> size X size, size is min(parentWidth, parentHeight)
function fitToParent(element) {
    // WORKAROUND
    element.width = 0;
    element.height = 0;
    
    // parent element is the .canvas-wrapper
    const parentWidth = element.parentElement.clientWidth;
    const parentHeight = element.parentElement.clientHeight;

    const size = Math.min(parentWidth, parentHeight);

    // for debug
    console.log('Wrapper size = ' + parentWidth + 'x' + parentHeight + ' Canvas size = ' + size + 'x' + size);

    element.width = size;
    element.height = size;
}

JSFiddle with workaround

EDIT

Apparently, canvas width and height attributes don't respect box sizing, hence the overflow from the border. Fixed by setting the css attributes to the same value as the element attributes (width, height -> style.width, style.height).

So, the second problem is fixed I think.

// width X height -> size X size, size is min(parentWidth, parentHeight)
function fitToParent(element) {
    // WORKAROUND
    element.width = 0;
    element.height = 0;
    element.style.width = 0;
    element.style.height = 0;
    
    // parent element is the .canvas-wrapper
    const parentWidth = element.parentElement.clientWidth;
    const parentHeight = element.parentElement.clientHeight;

    const size = Math.min(parentWidth, parentHeight);

    // for debug
    console.log('Wrapper size = ' + parentWidth + 'x' + parentHeight + ' Canvas size = ' + size + 'x' + size);

    element.width = size;
    element.height = size;
    element.style.width = size + 'px';
    element.style.height = size + 'px';
}

Final JSFiddle

Upvotes: 1

Related Questions