javascriptcssflexboxhtml5-canvasresize-observer

How to keep 1:1 aspect ratio for the child canvas element?


I have prepared a simple test web page at Github (using the main.css file) for my question:

animated screenshot

In a vertical flexbox layout I am trying to use ResizeObserver to watch a parent div element with flex-grow:1 and then keep the 1:1 aspect ratio on its child canvas element.

The reason I am trying to the keep the canvas aspect ratio is that in my real app a PixiJS canvas is embedded in a ReactJS app and if I just scale the canvas element to fill the div parent, then the PixiJS content is stretched:

screenshot

Unfortunately, the parent div element pushes the hint div element off the bottom of the screen -

I think, that something minor is missing in my code, please recommend a fix:

const parentElement = document.getElementById("parent");
const childElement = document.getElementById("child");

const resizeObserver = new ResizeObserver((entries) => {
  for (let entry of entries) {
    const { width, height } = entry.contentRect;
    const minDimension = Math.min(width, height);
    console.log(
      `parent: ${width} x ${height} -> child: ${minDimension} x ${minDimension}`
    );
    childElement.style.width = `${minDimension}px`;
    childElement.style.height = `${minDimension}px`;
  }
});

resizeObserver.observe(parentElement);
html,
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}

.flexRoot {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: stretch;
  width: 100%;
  height: 100vh;
}

/* the parent holds: status, canvas, hint */
.parent {
  border: 4px solid red;
  flex-grow: 1;
}

canvas {
  width: 1020px;
  height: 1020px;
  background-color: yellow;
  border: 4px green dotted;
  box-sizing: border-box;
}

.hint,
.status {
  background: lightblue;
  font-style: italic;
  text-align: center;
  flex-grow: 0;
}
<div class="flexRoot">
  <div class="status">Game #1 Score1:Score2</div>
  <div class="parent" id="parent">
    <canvas id="child"></canvas>
  </div>
  <div class="hint">A game hint to do this and that...</div>
</div>


Solution

  • To make sure the size of a .parent is determined by a .flexRoot instead of a #child it shouldn't allow an overflow:

    .parent {
      overflow: hidden;
    }
    

    Optional: a canvas is centered using flex.

    const parentElement = document.getElementById("parent");
    const childElement = document.getElementById("child");
    
    const resizeObserver = new ResizeObserver((entries) => {
      for (let entry of entries) {
        const {
          width,
          height
        } = entry.contentRect;
        const minDimension = Math.min(width, height);
        console.log(
          `parent: ${width} x ${height} -> child: ${minDimension} x ${minDimension}`
        );
        childElement.style.width = `${minDimension}px`;
        childElement.style.height = `${minDimension}px`;
      }
    });
    
    resizeObserver.observe(parentElement);
    html,
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
    
    .flexRoot {
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      align-items: stretch;
      width: 100%;
      height: 100vh;
    }
    
    
    /* the parent holds: status, canvas, hint */
    
    .parent {
      border: 4px solid red;
      flex-grow: 1;
      overflow: hidden;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    canvas {
      width: 1020px;
      height: 1020px;
      background-color: yellow;
      border: 4px green dotted;
      box-sizing: border-box;
    }
    
    .hint,
    .status {
      background: lightblue;
      font-style: italic;
      text-align: center;
      flex-grow: 0;
    }
    <div class="flexRoot">
      <div class="status">Game #1 Score1:Score2</div>
      <div class="parent" id="parent">
        <canvas id="child"></canvas>
      </div>
      <div class="hint">A game hint to do this and that...</div>
    </div>