r3f-scroll-rig

repository·master·Indexed 21 days ago

https://github.com/14islands/r3f-scroll-rig

A library for progressively enhancing React websites with WebGL using @react-three/fiber and smooth scrolling. It synchronizes Three.js objects with DOM elements via a persistent <GlobalCanvas/> and proxy elements, allowing seamless WebGL-HTML integrations. Key features include <SmoothScrollbar/> for synchronized scrolling, <UseCanvas/> for tunneling components to the global canvas, and <ScrollScene/> for tracking DOM elements to match 3D object positions and scales.

Tokens
11.9K
Snippets
41
Records
48
Agent score
75%

What's inside @14islands/r3f-scroll-rig

  1. Important considerations for UseCanvas

    master

    When using <UseCanvas/>, be aware of two key behaviors:

    1. HMR (Hot Module Replacement): HMR might not work for the children of <UseCanvas> unless you define those children outside the component.
    2. Reactivity: The props passed to children of <UseCanvas> are not reactive by default because the component is tunneled to the global canvas. To handle updated props, you must use the tunneling pattern described in the API documentation.
  2. How the scroll-rig architecture works

    master

    The library uses a single shared <GlobalCanvas/> that persists across page loads to avoid the browser limits of multiple WebGL contexts.

    To render WebGL content, React DOM components use the <UseCanvas/> tunnel component or the useCanvas() hook to send their children to this global canvas.

    To synchronize WebGL objects with the DOM during scrolling, the library uses "proxy" elements in the normal page flow. Components like <ScrollScene/>, <ViewportScrollScene/>, or the useTracker() hook detect the initial location and dimensions of these proxy elements and update the WebGL scene positions in lockstep with the scrollbar position on the main thread.

  3. Use relative scaling for consistent sizing

    master

    To ensure 3D objects scale correctly across different screen sizes, always base your mesh sizes on the scale value provided by ScrollScene, ViewportScrollScene, or the useTracker hook. This scale matches the tracked DOM element and updates automatically with media queries.

    The scale is a 3-dimensional vector from the vecn package, supporting swizzling (e.g., scale.xy) and object notation.

    <ScrollScene track={el}>
      {({ scale }) => (
        <mesh scale={scale} />
      )}
    </ScrollScene>
  4. Setup GlobalCanvas and SmoothScrollbar

    master

    To ensure WebGL objects and DOM content match perfectly, you must add <GlobalCanvas/> and <SmoothScrollbar/> to your application layout.

    Important: Keep <GlobalCanvas/> outside of your router so it does not unmount during navigation. <SmoothScrollbar/> is required to animate the browser scroll position on the main thread for synchronization.

    // Next.js example (_app.jsx)
    import { GlobalCanvas, SmoothScrollbar } from '@14islands/r3f-scroll-rig'
    
    function App({ Component, pageProps }) {
      return (
        <>
          <GlobalCanvas />
          <SmoothScrollbar />
          <Component {...pageProps} />
        </>
      )
    }
  5. Import scrollbar components from the subpath

    master

    To avoid bundling react-three-fiber unnecessarily, import the scrollbar-specific components and hooks from the @14islands/r3f-scroll-rig/scrollbar subpath instead of the main entry point.

    import { SmoothScrollbar, useScrollbar, useTracker } from '@14islands/r3f-scroll-rig/scrollbar'
  6. Use post-processing with GlobalCanvas

    master

    Because post-processing runs in a separate pass, you must disable the default global render loop to avoid double renders when using effects.

    <GlobalCanvas globalRender={false} scaleMultiplier={0.01}>
      <Effects />
    </GlobalCanvas>
  7. Match exact hex colors by disabling tone mapping

    master

    R3F uses ACES Filmic tone mapping by default. If you need to display editorial images or match specific hex colors exactly, disable tone mapping on the specific material.

    <meshBasicMaterial toneMapping={false} />
  8. Catch events from both DOM and Canvas

    master

    To allow R3F events to work correctly on a scrolling page, re-attach the event system to a parent DOM element of the canvas. This allows events to be captured from the DOM and tunneled into the canvas.

    const ref = useRef()
    return (
      <div ref={ref}>
        <GlobalCanvas
          eventSource={ref} // rebind event source to a parent DOM element
          eventPrefix="client" // use clientX/Y for a scrolling page
          style={{
            pointerEvents: 'none', // delegate events to wrapper
          }}
        />
      </div>
    )
  9. Fix Z-Fighting using scaleMultiplier

    master

    By default, the library calculates camera FoV so that 1 pixel = 1 viewport unit. On large screens, this results in very large scene units and high Z-axis distances, which can cause depth sorting glitches (Z-fighting).

    A performant way to fix this is to set the scaleMultiplier prop on GlobalCanvas to a smaller value (e.g., 0.01). This scales down the internal camera and scaling logic (e.g., 1000px becomes 10 viewport units).

    Note: If you change this setting, any hardcoded scales or positions in your scene will need to be updated.

    <GlobalCanvas scaleMultiplier={0.01} />
  10. How GlobalRenderer manages the render loop

    master

    The GlobalRenderer component is an internal mechanism used by r3f-scroll-rig to prevent double renders on the same frame and to manage complex rendering layers. It operates in two distinct phases using React Three Fiber's useFrame hook:

    1. Preload Phase: Executes tasks in the config.preloadQueue. It renders preload frames, clears the WebGL context, and then calls scrollRig.requestRender() and invalidate() to ensure the subsequent main render uses the correct visual state.
    2. Global Render Phase: Takes over the main rendering loop based on globalPriority. It manages camera layers by disabling all layers and then enabling only those present in the globalRenderQueue. If no specific queue is provided, it defaults to enabling layer 0. It also handles depth clearing via gl.clearDepth() if globalClearDepth is enabled in the canvas store.

    Note: As a component that returns null, it is intended to be part of the internal component tree to orchestrate the rendering lifecycle rather than being a UI component itself.

  11. Track a DOM element and render a Three.js object in its place

    master

    You can render a Three.js object that follows a specific DOM element by using <UseCanvas/> and <ScrollScene/>. The <ScrollScene/> component takes a track prop (a React ref to a DOM element) and provides transformation props to its children to match the element's scale and position.

    import { UseCanvas, ScrollScene } from '@14islands/r3f-scroll-rig'
    import { useRef } from 'react'
    
    export const HtmlComponent = () => {
      const el = useRef()
      return (
        <>
          <div ref={el}>Track me!</div>
          <UseCanvas>
            <ScrollScene track={el}>
              {(props) => (
                <mesh {...props}>
                  <planeGeometry />
                  <meshBasicMaterial color="turquoise" />
                </mesh>
              )}
            </ScrollScene>
          </UseCanvas>
        </>
      )
    }