Alex McMillan Alex McMillan - 3 months ago 13
CSS Question

Why does the BoundingClientRect not collapse when shrinking a flexbox-contained canvas?

I have a simple layout with a

<header>
, content
<div>
and a
<footer>
, wrapped inside a
<main>
using flexbox. The intention is for the canvas in the middle to grow/shrink when the viewport resizes while the header/footer remain static, with the 3 components combined always filling 100% of the viewport space without ever displaying scrollbars.

The content
<div>
resizes automatically when the viewport resizes (thanks to flex), but the canvas inside needs to be explicitly re-dimensioned.

I'm doing this with Javascript on the window's
resize
event, by requesting the content
<div>
s new dimensions via
getBoundingClientRect()
.

This works perfectly when increasing the height of the window, but refuses to work when shrinking. The
getBoundingClientRect()
function never seems to return a lesser value for
height
.

Resizing horizontally, for some reason, causes the
height
returned to increase dramatically. This is incredibly confusing.

I can't determine if this is an issue with my use of flexbox, my use of the canvas, or my use of
getBoundingClientRect()
.

Here is a fiddle showing the problem, and here are the relevant pieces of code:

HTML:

<main>
<header>
<h1>This is my header</h1>
</header>
<div id="content">
<canvas id="canvas"></canvas>
</div>
<footer>
<span>Simple footer text</span>
</footer>
</main>


CSS:

main {
/* Main page container with a standard column layout of header -> content -> footer */
display: flex;
flex-direction: column;

/* Ensure our main container is always 100% height */
min-height: 100vh;
}
canvas {
display: block; /* Avoiding unexplainable weird behaviour */
}
#content {
flex: 1; /* Stretch the content div to fill the <main> */
overflow-y: auto;
}


Javascript:

var contentDiv = document.getElementById('content');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
var pattern;

// Load up an image and create a pattern
img.onload = function() {
pattern = ctx.createPattern(img, 'repeat');
resize();
draw();
};
img.src = 'http://www.html5canvastutorials.com/demos/assets/wood-pattern.png';

// Resizes the <canvas> to match its parent, which has been sized by flex
function resize () {
// Here is the problem. INCREASING the height of the flexbox works
// perfectly. The canvas is resized appropriately. However, DECREASING
// it does nothing. Even though the contentDiv is sized by flexbox,
// its getBoundingClientRect() doesn't return a smaller height when
// shrinking it.
var boundingRect = contentDiv.getBoundingClientRect();
canvas.height = boundingRect.height;
canvas.width = boundingRect.width;
}

function draw () {
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = pattern;
ctx.fill();
}

window.addEventListener('resize', function () {
resize();
draw();
});

Answer

That's because you are using

min-height: 100vh;

Since you only impose a minimum value, when you reduce the window height, the canvas won't magically shrink, because the constraint already holds.

Instead, you should use

height: 100vh;

var contentDiv = document.getElementById('content');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
var pattern;

// Load up an image and create a pattern
img.onload = function() {
  pattern = ctx.createPattern(img, 'repeat');
  resize();
  draw();
};
img.src = 'http://www.html5canvastutorials.com/demos/assets/wood-pattern.png';

// Resizes the <canvas> to match its parent, which has been sized by flex
function resize () {
	// Here is the problem.  INCREASING the height of the flexbox works perfectly.  The canvas is resized appropriately.  However, DECREASING it does nothing.  Even though the contentDiv is sized by flexbox, its getBoundingClientRect() doesn't return a smaller height when shrinking it.
	var boundingRect = contentDiv.getBoundingClientRect();
	canvas.height = boundingRect.height;
  canvas.width = boundingRect.width;
}

function draw () {
  ctx.rect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = pattern;
  ctx.fill();
  
  ctx.font = "14pt Verdana";
  ctx.fillStyle = "lime";
  ctx.fillText('Resize the window vertically.', 40, 40);
  ctx.fillText('Watch the canvas increase in', 40, 70);
  ctx.fillText('height but NOT decrease!', 40, 100);
}
window.addEventListener('resize', function () {
	resize();
	draw();
});
html, body {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
header > h1 {
  margin: 0;
}
header, footer {
  background-color: #CCC;
}

main {
  /* Main page container with a standard column layout of header -> content -> footer */ 
  display: flex; 
  flex-direction: column;
  
  /* Ensure our main container is always 100% height */
  height: 100vh; 
}
canvas {
  display: block; /* Avoiding unexplainable weird behaviour */
}
#content {
  flex: 1; /* Stretch the content div to fill the <main> */
  overflow-y: auto;
}
<main>
  <header>
    <h1>This is my header</h1>
  </header>
  <div id="content">
    <canvas id="canvas"></canvas>
  </div>
  <footer>
    <span>Simple footer text</span>
  </footer>
</main>

Comments