Viewport width is inconsistent in React-three-fiber

I’m running into a viewport issue.
I’m setting the camera to a position based on the view width. When I check the view width on load, it comes out as one size, but if I just giggle the screen width, it updates to the correct size. How can I get it to recognize the correct size from the start?

I can’t use orbit controls because that breaks scrolling ability for mobile, so I have to make the camera position calculated with the width.

Most likely custom code or state issue - r3f automatically fills the entire available space of the Canvas container. Hard to say without seeing parts of the code related to camera positioning / viewport / how Canvasis mounted tho.

2 Likes

I’m sorry I completely forgot to paste my code:
A bit about what happens: when the viewer scrolls, it triggers an animation based from the experience. All of that works, it’s just getting the viewer width to position the camera is driving me nutz.

——————————————-
import { Canvas, useThree } from “@react-three/fiber”;

import “./App.css”;

import Experience from “./components/Experience”;

import { Suspense, useState,useEffect } from “react”;

import { LoadingScreen } from “./components/LoadingScreen”;

import ‘./components/ButtonsOverlay.css’

function ResponsiveCamera() {

  const { camera, viewport } = useThree();

  const { getCurrentViewport } = useThree(state => state.viewport)

  const { width, height } = getCurrentViewport()

  

  useEffect(() => {

    // Adjust camera position or other properties based on viewport dimensions

    console.log('Viewport:', viewport.width);

    if (viewport.width < 14.01) {

      // Example: For smaller screens, move camera closer

      camera.position.set(0, 0.5, 35);

      camera.fov = 50;

    }

    else {

      // Example: For larger screens, move camera further away

      camera.position.set(0, 0.5, 20);

      camera.fov = 50;

    }

    camera.updateProjectionMatrix();// Important to update after changing camera properties

    console.log("camera x: "+camera.position.x +" y:" +camera.position.y +" z:"+ camera.position.z );

  }, \[viewport.width, camera\],\[camera.position\]); // Re-run effect when viewport width changes

  return null; // This component doesn't render anything visually

}

function App() {

const [activeButton, setActiveButton] = useState(null);

const [start, setStart] = useState(false); // ← bring this back

const [hasScrolled, setHasScrolled] = useState(false);

const handleScroll = (offset) => {

if (offset > 0.01 && !hasScrolled) setHasScrolled(true);

if (offset <= 0.01 && hasScrolled) setHasScrolled(false);

};

// :link: Map button labels to URLs

const buttonLinks = {

"Video Production": "https://your-site.com/contact/videoproduction/",

"Design": "https://your-site.com/contact/branding/",

"Social Campaigns": "https://your-site.com/social",

"Interactive": "https://your-site.com/interactive",

"Contact": "https://your-site.com/contact"

};

return (

<>

 <LoadingScreen started={start} onStarted={() => setStart(true)} />

<div style={{ pointerEvents: 'none' }}>

 <div className= "stage" style={{ minWidth: "300px", width: "100%", height: "100vh" }}>

  <Canvas style={{ touchAction: 'pan-y'}} >



    <ResponsiveCamera />

    <Suspense fallback={null}>

      <Experience onActiveButtonChange={setActiveButton}

       onScrollChange={handleScroll} />

    </Suspense>    

  </Canvas>
  {/\* ✅ Pass state so loading screen button can hide itself \*/}

  {/\* === HTML Overlay OUTSIDE the Canvas === \*/}

  {!hasScrolled && <ScrollDownIndicator />}

  {/\* Overlay outside Canvas \*/}

  <div  >

    {activeButton && (

      <button className= "homeBut" 

        onClick={() => {

          const link = buttonLinks\[activeButton\];

          if (link) {

            window.open(link, "\_blank"); // new tab

          }

        }}

      >

        {activeButton}

      </button>

    )}

  </div>

  </div>

</>

);

}

function ScrollDownIndicator( ) {

return (

<div className= "scrollDown" >

  ↓ Scroll Down ↓

  <style>

    {\`

    @keyframes scrollPulse {

      0% { opacity: 0.4; transform: translateY(0); }

      50% { opacity: 1; transform: translateY(-10px); }

      100% { opacity: 0.4; transform: translateY(0); }

    }

  \`}

  </style>

</div>

);

}

export default App;