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

So, you know how changing the height of the canvas will scale in the content (scale the camera?) and keep the object in the vertical bounds, however this isn’t the case when you change the width of the canvas. Eventually you’ll be able to crop the scene. I get that this is natural, what I’m trying to do is create a canvas that will always keep the object within the bounds. This is what I mean

I’m not really asking how to do it, but probably more so if anyone has any ideas on how to approach this?

Things I can think of

• A function that will draw the camera back at a ratio to the width of the canvas, tricky when taking into account the current height of the canvas too

• Having an array of ‘responsive breakpoints’ for the camera distance, again tricky with the height of the canvas changing this

• Ummm, maybe keeping the canvas height exactly proportional to the fluid width? So the camera and canvas has a fixed aspect ratio

Does that make sense?

Actually I think my brain worked it out, what you really want to do is have CSS responsive breakpoints that adjust the height of the canvas itself, no need to mess with the scene

is this similar to background-size: cover in css? if yes, you can calculate it like this:

``````  const v = ... // viewport
const aspect = size.width / size.height

const adaptedHeight = height * (aspect > width / height ? v.width / width : v.height / height)
const adaptedWidth = width * (aspect > width / height ? v.width / width : v.height / height)
``````

the viewport is calculated like so:

``````  const getCurrentViewport = (camera, target, size) => {
const { width, height } = size
const distance = camera.position.distanceTo(target)
if (isOrthographicCamera(camera)) {
return { width: width / camera.zoom, height: height / camera.zoom, factor: 1, distance }
} else {
const fov = (camera.fov * Math.PI) / 180 // convert vertical fov to radians
const h = 2 * Math.tan(fov / 2) * distance // visible height
const w = h * (width / height)
return { width: w, height: h, factor: width / w, distance }
}
}
``````

this is how it looks, it scales the object (a plane in this case) based on the canvas viewport: https://codesandbox.io/s/r3f-floating-diamonds-dwkjr

Thanks mate, thats really helpful, and you’ve highlighted the ease of relating it to a background property. The property I’m actually trying to replicate is contain

Imagining that the square image in that pen is an object in your scene. Notice how a scene will automatically follow this behaviour in ThreeJS when adjusting the height, I’m just wondering if anyone has ideas about how to follow the behaviour of background-size: contain with both width and height. At the moment what I may need to do is force the height to be a predefined aspect ratio to a fluid width.

Is it related to this ? In this topic there is solutions to move the camera back and forth to fit an object in screen space.

1 Like

Thanks @felixmariotto, I did try @looeee’s script provided, it did a great a great job at centering toward the object, however it didn’t dynamically zoom out to keep the object in the view (without cropping) at smaller screen sizes, but I didn’t read the entire post so I may dig deeper into it

1 Like

@kylewetton did you check my updated pen here?

I totally get what you want. I want the same.

Actually, as far as “3D at the service of the web” is concerned (and not “web at the service of 3D”), it’s a crucial feature. Because we need our 3D scenes to be responsive, and some objects should remain in the viewport whatever its size.

I’m also building a scene where I need the main content of my 3D model (a set of 8 TV screens) to always be fully visible in the viewport. And I want it to take as much place as needed.

Here’s an image of what I mean:

In this scene, I have my 8 TV screen that should always be visible, with a small gap on sides:

Now you’ve seen the whole scene, here’s how it should look in the browser at different viewport sizes:

So it’s kinda like a `background-size: contain` applied on an invisible plane (the red zone I’ve drawn).
It should take as much width and height as it can, while keeping its aspect ratio, but should never exceed the width nor the height of the viewport.

I know it’s related to `fov` and a lot of maths, but I’m not a wizard at maths… sigh…

At least I can explain this problem with images.

Ok, I start to have an idea.

Let’s say that our `canvas` takes the whole viewport.

We’ll call the red zone the “plane”, and we’ll assume its aspect-ratio to be 4 / 3 (I know it’s not, but shush…).

We have two scenarios:

1. The `window.innerWidth` is too large or the `window.innerHeight` is too small ;
2. The `window.innerWidth` is too small or the `window.innerHeight` is too large ;

# 1. window too large or not tall enough

The first situation happens when the viewport’s aspect-ratio is bigger than the plane’s aspect-ratio.

If the viewport has an aspect-ratio of `16 / 9 = 1.778`, it’s bigger than the plane’s one (`4 / 3 = 1.333`).

In this case, we can use the default camera behavior provided in the threejs docs:

``````camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
``````

Because the `camera.fov` only controls one axis (I think the vertical one).

So as we want our plane to take full screen height, this does the job.

# 2. window too tall or not large enough

The second situation happens when the viewport’s aspect-ratio is smaller than the plane’s aspect-ratio.

If the viewport has an aspect-ratio of `9 / 16 = 0.5625`, it’s smaller than the plane’s one (`4 / 3 = 1.333`).

In this case, we want the plane to take full screen width.

We can’t rely on the default `fov` of threejs, because we need the camera to see up and down but not left or right (horizontal fov?).

I’ve found this: https://stackoverflow.com/a/26665260/4936168
This formula should allow us to change camera’s horizontal fov, but I’ve not tested it yet.
Not even sure if this is the correct approach.

More on this tomorrow…

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;

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;
} 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;
}
``````

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;
``````
11 Likes

@nagman
Cool! This is just like overscan, really cool, this should be a core functionality "THREE. perspectiveCamera({ overscan: true});

Legend! Sorry about the slow reply I just saw this. Well done mate

Just wanted to say thank you so much @nagman! I’ve been trying to get this behavior for a scene but didn’t think about using hFOV which makes things so much simpler!

1 Like

@nagman you’re a god among men. Thanks for sharing.

1 Like