How to make camera adapt to different screen size. (responsive camera)

Hi,
How do i setup for camera to prevent from objects deformation on screen size changes?
I tried to set a orthographic camera (did not solve the issue)

     <Canvas
          {...props}
          eventSource={null}
          style={{ pointerEvents: 'none', position: 'absolute', zIndex: -1 }}
        >
                    <ResponsiveOrthographicCamera/>

        </Canvas>

const ResponsiveOrthographicCamera = (props) => {
  const ref = useRef()
  const [size, setSize] = useState([window.innerWidth, window.innerHeight])

  useEffect(() => {
    const handleResize = () => {
      setSize([window.innerWidth, window.innerHeight])
    }

    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('resize', handleResize)
    }
  }, [])

  useEffect(() => {
    const aspect = size[0] / size[1]
    ref.current.left = -1 * aspect
    ref.current.right = 1 * aspect
    ref.current.top = 1
    ref.current.bottom = -1
    ref.current.updateProjectionMatrix()
  }, [size])

  return <OrthographicCamera makeDefault ref={ref} {...props} />
}

versions:
“three”: “0.160.0”,
“three-stdlib”: “^2.28.9”,
“@react-three/drei”: “^9.92.7”,
“@react-three/fiber”: “8.15.12”,

all drei cameras as responsive by default, you don’t need to do anything. nothing should deform just because you film orthographic. but if that wasn’t the case could you make a quick repro?

Hi,
What i need to do is make object like image or sprite remain it’s aspect ratio regardless of screen size changes.
for example, i tried to set fixed aspect ratio but it didn’t work.

function Items() {
  const ref = useRef()

  // Load the image using THREE.TextureLoader
  const texture = useLoader(TextureLoader, 'https://dummyimage.com/600x400/000/fff')
  const planeSize = 1
  const { width, height } = texture.image
  const aspect = width / height

  return (
    <mesh ref={ref}>
      <planeGeometry args={[planeSize * aspect, planeSize]} />
      {/* Use meshBasicMaterial or meshStandardMaterial */}
      <meshBasicMaterial map={texture} side={DoubleSide} />
    </mesh>
  )
}

i think what you mean is object fit/camera zoom to fit

use this GitHub - pmndrs/drei: đŸ„‰ useful helpers for react-three-fiber

or GitHub - pmndrs/drei: đŸ„‰ useful helpers for react-three-fiber

<Bounds fit clip observe>
  <Model />
</Bounds>

try it here, make the preview window smaller/bigger and see it adapt

or GitHub - yomotsu/camera-controls: A camera control for three.js, similar to THREE.OrbitControls yet supports smooth transitions and more features. it has a fitToBox method. there’s also a <CameraControls> in drei ofc

1 Like

Thank you, this is quite useful. But it did not solve my issue.

1709098272978
my plane geometry aspect ratio is still effected by screen size. i guess i misused word “responsive”, what i need is to prevent the image from distorted. Ideally this item should be “responsive” and yet undistorted, otherwise this image won’t look good on mobile device.

I assume there are a couple of ways, maybe use OrthographicCamera, or maybe update the aspect ratio on window resize. but none of them really worked

export function Items() {
  const ref = useRef()

  const texture = useLoader(TextureLoader, 'https://dummyimage.com/600x400/000/fff')

  // Load the image using THREE.TextureLoader
  const aspect = texture.image.width / texture.image.height
  let scaleX = 1
  let scaleY = 1
    const desiredAspect = 600 / 400

  if (aspect > desiredAspect) {
    // Texture is wider than the desired aspect ratio
    scaleY = (1 / aspect) * desiredAspect
  } else {
    // Texture is taller than or equal to the desired aspect ratio
    scaleX = aspect / desiredAspect
  }
  return (
    <group scale={[scaleX, scaleY, 1]}>
      <mesh ref={ref}>
        <planeGeometry />
        <meshBasicMaterial map={texture} side={DoubleSide} />
      </mesh>
    </group>
  )
}
const CustomOrthographicCamera= (props) => {
  const frustumSize = 5

  const camera = useRef()
  const { size } = useThree()

  useEffect(() => {
    const aspect = size.height / size.width
    camera.current.left = -frustumSize / 2
    camera.current.right = frustumSize / 2
    camera.current.top = (frustumSize * aspect) / 2
    camera.current.bottom = (-frustumSize * aspect) / 2
    camera.current.updateProjectionMatrix()
  }, [size, frustumSize])

  return <OrthographicCamera ref={camera} {...props} />
}

i also tried

          <PerspectiveCamera
            fov={60}
            position={[0, 0, -4]}
            manual
            aspect={size.width / size.height}
            onUpdate={(c) => c.updateProjectionMatrix()}
          />

Did i miss anything?

note: i found a similar issue here: Resize canvas with different aspect ratio - #7 by HasanTheSyrian
his method is update the camera aspect ratio onresize

i don’t understand what you do with these cameras. it stretches because you set them up manually which would involve some math. but this is what these cameras would normally do automatically, that’s why they exist, to remove that burden.

it’s just this

<PerspectiveCamera makeDefault position={[0, 0, 5]} fov={85} />

or

<OrthographicCamera makeDefault position={[0, 0, 5]} zoom={100} />

you don’t even have to define them, you can use the default camera

<Canvas camera={{ position: [0, 0, 5], fov: 85 }}>...</Canvas>
<Canvas orthographic camera={{ position: [0, 0, 5], zoom: 100 }}>...</Canvas>

you can of course use plain three cameras, or drei cameras with the manual prop, or overwrite left/right/top/bottom, but then you need to know how to calculate aspect ratio.

the clone demo i linked above uses orthographic camera, and drei/bounds to adapt the model to screen size, it doesn’t stretch.

Using default camera is what i did in the first place. Things got stretches is the reason i tried other ways.
Thank you for your help though.

could you make a codesandbox that exhibits the problem, that way we can sort it out quick im sure.

what happend when ‘screen size changed’,
because i test it in chrome,when Browser window change,the canvas doesn’t change ,doesn’t move