Why does my scrolling Three Fiber camera stutter when new divs come into view

38 views Asked by At

I created a website that has a background consisting of floating spheres using Three Fiber. The <Canvas /> camera scrolls along with the page, but the camera movement seems to stutter whenever a new section <div /> comes into view. This is most pronounced on mobile or lower end devices, but can still slightly be seen on more powerful desktops.

The site is https://www.brownbearepoxy.com.

This is the main snippet from my background component:

/** Adjusts camera position in respect to page scroll position */
function ScrollCamera(): React.ReactElement | null {
  const scrollPos = useRecoilValue(scrollState);
  const transitionSpeed = 0.0032;
  const lerp = 0.16;

  useFrame((state, dt) => {
    if (dt < 0.1) {
      const newY = transitionSpeed * scrollPos * -1;
      const position = new THREE.Vector3(0, newY, 10);
      state.camera.position.lerp(position, lerp);
    }
  });

  return null;
}

/** Base component handling ThreeJS configurations and custom spheres */
export default function Spheres(): React.ReactElement {
  const sp = useRecoilValue(scrollPercentState);

  return (
    <Canvas
      gl={{ antialias: true }}
      dpr={[1, 1.5]}
      camera={{
        position: [0, 0, 10], fov: 20, near: 0.01, far: 95,
      }}
      // Dynamically adjust canvas style with scroll position
      style={{
        filter: `blur(${sp * 12}px)`,
        opacity: 1 - (sp * 0.4),
      }}
    >
      <ScrollCamera />
      <directionalLight color="white" intensity={1.5} position={[-2, 8, 16]} />
      <ambientLight intensity={0.4} />
      <HighFidelity />
      <LowFidelity />
    </Canvas>
  );
}

The scroll position is set in the root of my app using a dedicated component and a scroll event listener:

function Scroll(props: IScrollProps): React.ReactElement | null {
  const { scrollRef } = props;
  const setScroll = useSetRecoilState(scrollState);

  const handleScroll = () => {
    if (scrollRef?.current) {
      setScroll(scrollRef.current.scrollTop);
    }
  };

  /** Configures scroll event listeners to update position value */
  useEffect(() => {
    if (scrollRef?.current) {
      scrollRef.current.addEventListener('scroll', handleScroll);
    }

    return () => {
      scrollRef?.current?.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return null;
}

Each section of the website uses a parent <div /> with className={sx.page} and an inner <div /> with className={sx.content}. For example, the about section looks like this.

    <div
      id="about"
      ref={divRef}
      className={sx.page}
      key="about"
    >
      <div className={sx.content}>
        <MotionContent key="about">
          <h1 className={sx.head2}>About</h1>
          <div className={sx.text}>
            {paragraph1}
            {paragraph2}
          </div>
        </MotionContent>
      </div>
    </div>

The Sass styles for .page and .content are

.page {
  width: 100%;
  height: auto;
  min-height: 100%;
  min-width: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
  padding: 10vh 0;
}

.content {
  width: 100%;
  max-width: $max-width;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  padding: $space-huge;
  row-gap: $space-large;
}

Each section is also wrapped in <MotionContent /> which is a Framer Motion <motion.div /> using whileInView to animate the presence when each section's content comes into view.

I originally thought the whileInView animation was causing the stuttering so I removed it using just the .page and .content <div /> elements as described above, yet the camera still lags when each section comes into view. Without the Framer Motion stuff in place, I added if (dt > 0.001) console.log(dt) within the <ScrollCamera /> component's useFrame hook to check when the frame delta was too large and it lines up exactly with each section coming into the view port.

The current configuration that's currently hosted is the most optimized version I could get, but I'm struggling to understand why I can't get the camera to scroll smoothly from top to bottom. I would love to understand this better, any help would be much appreciated!

0

There are 0 answers