0

I have a canvas element inside a flexbox that may be resized. How do I get the canvas to fill the available space without losing its aspect ratio (2/3), and without being cut off?

(I want to change the CSS dimensions, not the canvas resolution).

I've tried using object-fit: contain and clamp(...), but I can't get the results I want. Either the canvas doesn't maintain its aspect ratio, or it grows outside its container.

body {
  margin: 0;
}

#mainContent {
    background: grey;
    height: 100vh;
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
}

#someOtherElem {
    background: red;
    width: 200px;
    height: 200px;
    margin-left: 1rem;
}

#canvasContainer {
    display: flex;
    flex: 1;
    height: 100%;
    justify-content: center;
    align-items: center;
}

canvas {
    width: calc(100% - 2rem);
    height: calc(100% - 2rem);
    background: green;
    object-fit: contain;
}
<div id="mainContent">
  <div id="someOtherElem"></div>
  <div id="canvasContainer">
    <canvas height="300" width="200"></canvas>
  </div>
</div>

Here's a stripped-down version of what I've been trying: https://jsfiddle.net/mwq4502v/.

I'm not sure what the best way to achieve this is, so any help would be greatly appreciated!

Chris
  • 358
  • 1
  • 14
  • There is no fixed size for neither `canvas` nor `#canvasContainer`. What exactly is the aspect ratio you want? – InSync Apr 06 '23 at 17:31
  • "fill the available space without losing its aspect ratio, and without being cut off" seems impossible unless you're OK with blank padding, or I'm missing something. There are two cases: either the resized space is the right aspect ratio (unlikely), then you can fill it without any cutoff, or it isn't, in which case you either need to change the aspect ratio, chop something off, or have some blank space padding somewhere. Are you OK with the padding? Also, code should be in the post itself as a [mcve]. Thanks. – ggorlen Apr 06 '23 at 17:36
  • @InSync I want a ratio of 2/3, sorry, it's set in the HTML but I forgot to make that clearer – Chris Apr 06 '23 at 17:42
  • @ggorlen Yeah, I'm wanting blank padding at either axis – Chris Apr 06 '23 at 17:43
  • Ignore the top/accepted answer in the duplicate target and go straight to the second one using `aspect-ratio`. – TylerH Apr 07 '23 at 15:09

1 Answers1

1

You can specify an aspect-ratio of 2 / 3 for canvas and a width of 100% or <parent-height> * 2 / 3, whichever smaller.

canvas {
  aspect-ratio: 2 / 3;
  width: min(100%, 100vh * 2 / 3);
  /* ...or height: min(100%, calc(100vw - 210px) * 3 / 2). Same spirit. */
}

Some math:

Let the width and height of the container be w and h, correspondingly. Since the canvas needs to be as large as possible it will always touch at least two borders of the container (or all 4), which means its size can be either w / (w / (2 / 3)) or (h * 2 / 3) / h, depends on the size of the container.

w / h > 2 / 3:

◄──────────── w ────────────►
┌────────┬─────────┬────────┐ ▲
│        │         │        │ │
│        │         │        │ │
│        │         │        │ │
│        │         │        │ h
│        │         │        │ │
│        │         │        │ │
│        │         │        │ │
└────────┴─────────┴────────┘ ▼
         ◄ (h*2/3) ►

w / h < 2 / 3:

         ◄─── w ───►
         ┌─────────┐ ▲
         │         │ │
       ▲ ├─────────┤ │
       │ │         │ │
       │ │         │ │
 (w*3/2) │         │ h
       │ │         │ │
       │ │         │ │
       ▼ ├─────────┤ │
         │         │ │
         └─────────┘ ▼

This means the width needs to be min(w, h * 2 / 3) or, in CSS, min(100%, 100vh * 2 / 3).

Try it:

canvas {
  aspect-ratio: 2 / 3;
  width: min(100%, 100vh * 2 / 3);
}

/* Demo only */

#canvasContainer {
  outline: 1px solid #000; /* Just so we know where it is */
}

body {
  margin: 0;
}

#mainContent {
  display: flex;
  align-items: center;
  height: 100vh;
}

#someOtherElem {
  margin-left: 1rem;
  width: 200px;
  height: 200px;
  background: red;
}

#canvasContainer {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}

canvas {
  background: green;
}
<div id="mainContent">
  <div id="someOtherElem"></div>
  <div id="canvasContainer">
    <canvas></canvas>
  </div>
</div>
InSync
  • 4,851
  • 4
  • 8
  • 30
  • Thanks, this works great for resizing vertically, but if you resize horizontally the canvas doesn't shrink and can overflow the container. – Chris Apr 06 '23 at 19:20
  • @Chris I can't reproduce that using the snippet above. What was the viewport size? – InSync Apr 06 '23 at 22:05
  • In the desired result, after the canvas meets the red element, it should shrink with blank space being added on the top/bottom. In the snippet above, the canvas is not affected by the width of the container, so only adjusts according to the container's height and remains the same size even when it is larger than the container horizontally. – Chris Apr 06 '23 at 22:31
  • @Chris Updated. See if it works. – InSync Apr 06 '23 at 23:04