I'm using three.js to render to a canvas. I would like to have one div
positioned above the canvas and another below. The canvas should fill the remaining space in between (even if it means the canvas changes aspect ratio whenever the viewport size changes, that's okay). The 3 elements together should fill 100% of the viewport at all times, but not exceed it and cause scrollbars to appear.
In case it's not clear, here's a gif I made (which uses a static image instead of a canvas) demonstrating exactly what I want to achieve:
I figured this would be a straightforward flexbox layout, but I can't seem to get it to work. Either scrollbars appear, or the bottom element gets pushed beyond the viewport. Here's one of my attempts (click Run code snippet > Full page):
const canvas = document.querySelector('canvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera();
camera.position.z = 5;
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: '#ff0000' });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
requestAnimationFrame(render);
function render() {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
if (resizeRendererToDisplaySize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function resizeRendererToDisplaySize(renderer) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
html, body {
height: 100%;
margin: 0;
}
.container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.top, .bottom {
padding: 10px;
}
.middle {
flex: 1;
}
.middle canvas {
width: 100%;
height: 100%;
display: block;
}
<div class="container">
<div class="top">Top</div>
<div class="middle">
<canvas></canvas>
</div>
<div class="bottom">Bottom</div>
</div>
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@v0.155.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@v0.155.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
window.THREE = THREE;
</script>
Strangely, if you change the flex-direction
to row
, you'll see it works exactly how I want it to, but horizontally. I would simply like it to work vertically. Is it possible?
Found the solution here. Since the flex-direction
is column
, the min-height
of the middle element needs to be set to zero.
const canvas = document.querySelector('canvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera();
camera.position.z = 5;
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: '#ff0000' });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
requestAnimationFrame(render);
function render() {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
if (resizeRendererToDisplaySize(renderer)) {
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function resizeRendererToDisplaySize(renderer) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
html, body {
height: 100%;
margin: 0;
}
.container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.top, .bottom {
padding: 10px;
}
.middle {
flex: 1;
min-height: 0;
}
.middle canvas {
width: 100%;
height: 100%;
display: block;
}
<div class="container">
<div class="top">Top</div>
<div class="middle">
<canvas></canvas>
</div>
<div class="bottom">Bottom</div>
</div>
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@v0.155.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@v0.155.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
window.THREE = THREE;
</script>