Keeping an object scaled based on the bounds of the canvas (really battling to explain this one)

Yup! Managed it!

Actually I just reversed a snippet that I used for this:

In this case, I wanted to have a background-size: cover effect on my scene.

Here’s the snippet I used:

import { MathUtils } from 'three';

const fov = 50;
const planeAspectRatio = 16 / 9;

window.addEventListener('resize', () => {	
	camera.aspect = window.innerWidth / window.innerHeight;
	
	if (camera.aspect > planeAspectRatio) {
		// window too large
		const cameraHeight = Math.tan(MathUtils.degToRad(fov / 2));
		const ratio = camera.aspect / planeAspectRatio;
		const newCameraHeight = cameraHeight / ratio;
		camera.fov = MathUtils.radToDeg(Math.atan(newCameraHeight)) * 2;
	} else {
		// window too narrow
		camera.fov = fov;
	}
})

To get a background-size: contain effect, you just need to reverse the conditions:

	if (camera.aspect > planeAspectRatio) {
		// window too large
		camera.fov = fov;
	} else {
		// window too narrow
		const cameraHeight = Math.tan(MathUtils.degToRad(fov / 2));
		const ratio = camera.aspect / planeAspectRatio;
		const newCameraHeight = cameraHeight / ratio;
		camera.fov = MathUtils.radToDeg(Math.atan(newCameraHeight)) * 2;
	}

And voilà!

Explanation

For anyone who wants to understand this snippet, I actually do exactly what I explain in the previous answer: if the window is too narrow (or too tall), I change the camera fov on the x axis (horizontally) instead of changing it on the y axis (the default behaviour).

I’ve used the formula given here: three.js - 90 degree field of view without distortion in THREE.PerspectiveCamera - Stack Overflow

hFOV = 2 * Math.atan( Math.tan( camera.fov * Math.PI / 180 / 2 ) * camera.aspect ) * 180 / Math.PI; // degrees

It’s just split into multiple lines for better clearness (but that’s the same formula):

const cameraHeight = Math.tan(MathUtils.degToRad(fov / 2));
const ratio = camera.aspect / planeAspectRatio;
const newCameraHeight = cameraHeight / ratio;
camera.fov = MathUtils.radToDeg(Math.atan(newCameraHeight)) * 2;
16 Likes