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)
  return [adaptedWidth * factor, adaptedHeight * factor, 1]

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?