<!--
Based on:
https://discourse.threejs.org/t/functions-to-calculate-the-visible-width-height-at-a-given-z-depth-from-a-perspective-camera/269/28
-->
<style>
html,
body,
canvas {
display: block;
height: 100%;
margin: 0;
background-color: deeppink;
}
</style>
<canvas></canvas>
<script type="module">
import {
WebGLRenderer,
Scene,
PerspectiveCamera,
PlaneGeometry,
Mesh,
MeshBasicMaterial,
TextureLoader
} from "https://unpkg.com/three@0.154.0/build/three.module.js";
const { atan, tan } = Math;
// This is like the CSS `perspective` property (https://developer.mozilla.org/en-US/docs/Web/CSS/perspective)
const perspective = 800; // start with perspective
const V_FOV_DEG =
(180 * (2 * atan(window.innerHeight / 2 / perspective))) / Math.PI;
// --- or ---
// const V_FOV_DEG = 45; // start with fov
// const perspective = ...solve for perspective...;
const V_FOV_RAD = Math.PI * (V_FOV_DEG / 180);
const TAN_HALF_V_FOV = tan(V_FOV_RAD / 2);
const texture = new TextureLoader().load(
"https://upload.wikimedia.org/wikipedia/commons/f/f4/The_Scream.jpg",
// Don't render until we have the image loaded, otherwise if we render sooner, we will have unknown image size.
onLoad
);
function onLoad() {
const img = texture.image;
const imgWidth = img.naturalWidth;
const imgHeight = img.naturalHeight;
const imgAspect = imgWidth / imgHeight;
/** @param {'cover' | 'contain'} fitment */
const getDistanceFromCamera = (fitment = "contain") => {
const viewAspect = window.innerWidth / window.innerHeight;
return (
fitment === "contain" ? imgAspect <= viewAspect : imgAspect > viewAspect
)
? imgHeight / (2 * TAN_HALF_V_FOV) // Use the object size here! Not the viewport size!
: imgWidth / (2 * tan(vfovToHfov(viewAspect) / 2));
};
const renderer = new WebGLRenderer({
canvas: document.querySelector("canvas"),
antialias: true,
precision: "highp"
});
renderer.setPixelRatio(window.devicePixelRatio);
const scene = new Scene();
const camera = new PerspectiveCamera(V_FOV_DEG, 1, 100, 30000);
scene.add(camera);
const plane = new Mesh(
new PlaneGeometry(imgWidth, imgHeight),
new MeshBasicMaterial({
map: texture
})
);
scene.add(plane);
function resize() {
renderer.setSize(window.innerWidth, window.innerHeight, false);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
// const distanceFromCam = getDistanceFromCamera("contain");
// --- or ---
const distanceFromCam = getDistanceFromCamera("cover");
camera.position.z = distanceFromCam;
// --- or ---
// camera.position.z = perspective; // Like CSS, distance from camera to screen plane
// plane.position.z = perspective - distanceFromCam; // Move the object instead
// Now you might understand how CSS perspective works!
}
window.addEventListener("resize", resize);
// Set initial values
resize();
requestAnimationFrame(function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
});
}
/**
* @param {number} aspect - The camera aspect ratio, which is generally width/height of the viewport.
* @returns {number} - The horizontal field of view derived from the vertical field of view.
*/
function vfovToHfov(aspect) {
return atan(aspect * TAN_HALF_V_FOV) * 2;
}
</script>
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.