3D hero sections in React Three Fiber that stay fast

How to add an interactive 3D object to a React landing page with React Three Fiber and drei, without wrecking load time, battery life or accessibility.

Moveset team··2 min read

A well-made 3D object in a hero section makes a product feel tangible in a way a flat screenshot can't. A badly made one drains laptop batteries, stutters on phones and pushes your largest contentful paint back by seconds. This guide covers the practical decisions that separate the two, using React Three Fiber (R3F), drei and the patterns behind Moveset's 3D components.

Setup

R3F is a React renderer for three.js. You describe the scene as JSX, and R3F manages the render loop.

bash
npm i three @react-three/fiber @react-three/drei

Moveset's 3D components take the container element as a prop and fill it, which keeps layout in your hands:

tsx
"use client";
import { useEffect, useRef, useState } from "react";
import Orb from "@/components/moveset/hero-object-3d";

export function Hero3D() {
  const ref = useRef<HTMLDivElement>(null);
  const [el, setEl] = useState<HTMLDivElement | null>(null);
  useEffect(() => setEl(ref.current), []);
  return (
    <div ref={ref} style={{ position: "relative", height: 520 }}>
      {el && <Orb el={el} variant="chrome" />}
    </div>
  );
}

Rule 1: never block the first paint on WebGL

three.js is a large dependency. Your headline, copy and call to action should render before any of it downloads. Two techniques:

  • Lazy-load the scene. In Next.js use dynamic(() => import("./Hero3D"), { ssr: false }); elsewhere use React.lazy with Suspense.
  • Render a static fallback first. A gradient or a pre-rendered image in the same position means nothing shifts when the canvas fades in.

Moveset mounts its canvas with zero opacity and fades it in once it has mounted, so there is no flash of an empty black box.

Rule 2: cap the device pixel ratio

Rendering at a full 3x pixel ratio on a high-density phone means drawing nine times as many pixels as 1x. The visual gain over 1.5x is small; the cost is not. R3F's dpr prop accepts a range:

tsx
<Canvas dpr={isMobile ? [1, 1.5] : [1, 2]} gl={{ antialias: true, alpha: true }}>
  {/* scene */}
</Canvas>

For adaptive quality on slower hardware, drei's PerformanceMonitor can lower the DPR when frame rate drops.

Rule 3: stop rendering when nobody is looking

By default R3F renders every frame. A hero scrolled out of view does not need to. Moveset's shared Stage wraps the canvas in an IntersectionObserver and switches R3F's frameloop between "always" and "never":

tsx
const [visible, setVisible] = useState(true);
useEffect(() => {
  const io = new IntersectionObserver(([e]) => setVisible(e.isIntersecting), { rootMargin: "100px" });
  io.observe(el);
  return () => io.disconnect();
}, [el]);

<Canvas frameloop={visible ? "always" : "never"} /* ... */ />

If your scene only changes in response to input, frameloop="demand" goes further: R3F renders only when something calls invalidate().

Rule 4: make interaction feel physical

Drag-to-rotate is the most natural interaction for a hero object. Store the angular velocity while dragging, then let it decay in useFrame after release. That inertia is what makes an object feel like it has mass, the same idea as a spring in 2D UI.

tsx
useFrame((_, dt) => {
  if (!dragging.current) velocity.current *= Math.pow(0.04, dt); // exponential decay, frame-rate independent
  mesh.current.rotation.y += velocity.current * dt;
});

Multiplying by dt keeps the motion identical on 60Hz and 120Hz displays. Also set touch-action: pan-y on the canvas so vertical swipes still scroll the page on mobile.

Rule 5: respect reduced motion

Auto-rotation and floating loops are exactly what prefers-reduced-motion exists for. When it is set, keep the object static (or rotate only on direct drag) and skip intro animations.

Choosing the right 3D component

  • Hero Object 3D (free): a chrome or glass object with drag inertia. A safe default for almost any product.
  • Card Stack 3D (free): a spring carousel of cards, good for showing several features or plans.
  • Globe 3D, Product Spin 3D, Scroll Camera Scene, Particle Morph Text and 3D Card Flip (Pro): for launches where 3D is the story.

Treat 3D like a hero image: one strong object, loaded after the content, paused when off-screen.