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;