AETumi — AI-native 3D web platform for Three.js, WebGL and interactive websites
News & Guides

How to Build an Animated Three.js Hero Section

September 8, 2026 · AETumi

Key answer: A Three.js hero section is a full-viewport WebGL canvas placed behind your real headline and call-to-action. It renders one lightweight, animated 3D object with a render-on-demand loop, and layers semantic HTML text on top for accessibility and search. Build it with React Three Fiber when your site is already React, or with plain Three.js r160 ES modules when it is not, keep the scene to a single hero-scale asset, cap the device pixel ratio, and gate every animation behind visibility and reduced-motion checks so the effect never taxes the page.

That answers the first thing anyone asks — what is a Three.js hero section — and frames the discipline that separates a hero that converts from one that quietly wrecks your metrics. The rest is the how, the trade-offs, and the code.

How it works, step by stepAETumi technical diagram — How it works, step by stepStart from owned sourceAdapt props & variantsWire server/client boundaryHandle loading/empty/errorShip & reuse
How it works, step by step

Table of contents

What you'll learn

  • The anatomy of a Three.js hero section and why the 3D is the stage, not the script.
  • A minimal React Three Fiber hero, a vanilla Three.js r160 equivalent, and more focused demos you can lift into production.
  • How to protect Largest Contentful Paint and Cumulative Layout Shift while still shipping a cinematic surface.
  • When a 3D hero is the right call, when a static image or looping video wins instead, and how to decide with a matrix.

What a 3D hero section actually is

A hero section is the first full screen a visitor sees. A 3D hero section swaps the flat background image or looping video for a live Three.js scene rendered on a WebGL canvas. The point is not spectacle for its own sake — it is to communicate motion, material, and craft in the first two seconds, while your headline still does the selling.

Ascend
Ascend — live preview from the AETumi library

The mistake most teams make is treating the 3D as the content. It is the stage, not the script. The headline, subhead, and CTA remain plain HTML on top of the canvas. That separation is what keeps the hero readable, indexable, and fast. When people search three.js hero section, they usually mean this specific pattern: one animated object, a headline in front, and a page that still loads quickly on a mid-range phone.

A WebGL canvas is a bitmap the GPU paints every frame — no text nodes, no links, nothing a crawler or screen reader can parse. So the canvas carries the feeling, and ordinary DOM carries the meaning. Get that division right and performance, accessibility, and SEO all follow from it.

Before vs afterAETumi technical diagram — Before vs afterRegenerate each timeOwn a component sourceInconsistent outputConsistent, reusableGeneric defaultsDistinctive, themedHard to maintainOne source of truthNo a11y guaranteesAccessible by default
Before vs after

Why a 3D hero matters

The hero is where bounce decisions happen. A generic stock photo says "template"; a live, reactive 3D object says "this company builds things." For product, agency, and portfolio sites, that first impression is measurable in scroll-depth and time-on-page. An animated hero Three.js scene gives you a differentiated surface without a video budget, and — unlike a baked video — it can respond to the pointer or the scroll position.

Gravity
Gravity — live preview from the AETumi library

It matters commercially, too. A WebGL hero is the most reused component across modern 3D sites, so getting the pattern right once pays off on every project. But the same weight that makes it impressive makes it dangerous: an unmanaged scene can push a multi-megabyte model in front of first paint and hand you a failing Core Web Vitals score. The value is real, and so is the cost.

Hero architecture: the three-layer model

Think of a 3D hero as three stacked layers, from back to front:

Lens Refraction
Lens Refraction — live preview from the AETumi library

1. The WebGL canvas — a single <canvas> pinned to 100vw × 100vh, position: fixed or absolute, z-index: 0. It holds one hero-scale object (a product, an abstract form, a logo mark) with clean lighting and, ideally, an environment map for believable reflections. 2. The DOM overlay — real <h1>, <p>, and <a>/<button> elements at a higher z-index, with pointer-events managed so the CTA stays clickable while the rest of the overlay lets pointer events fall through to the canvas. 3. The scroll cue — an optional indicator that hints the page continues, often tied to a 3D scroll sequence below the fold.

Restraint wins. One rotating object with a good HDRI-style environment and a soft key light reads as premium; five objects, bloom, and particles read as a tech demo. If you are assembling a hero from a kit, AETumi's 3D components collection is built around this single-object-first principle. The diagram for this section shows the same stack visually — canvas at the back, HTML overlay in the middle, scroll cue at the front — and keeping those layers separate is the whole architecture.

Technical implementation and code demos

Below are focused, correct demos. Each does one thing. Read the note under each block before you paste it.

Orange Monolith Grove
Orange Monolith Grove — live preview from the AETumi library

Demo 1 — A minimal React Three Fiber hero. This renders one animated mesh, respects reduced-motion, and uses frameloop="demand" so the loop only advances when you explicitly invalidate it.

import { Canvas, useFrame, invalidate } from "@react-three/fiber";
import { Environment } from "@react-three/drei";
import { useRef } from "react";

function HeroObject() {
  const mesh = useRef();
  const reduced =
    typeof window !== "undefined" &&
    window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  useFrame((_, delta) => {
    if (reduced || !mesh.current) return;
    mesh.current.rotation.y += delta * 0.4;
    invalidate(); // keep the on-demand loop alive for the next frame
  });

  return (
    <mesh ref={mesh}>
      <icosahedronGeometry args={[1.2, 0]} />
      <meshStandardMaterial color="#8a5cff" roughness={0.25} metalness={0.6} />
    </mesh>
  );
}

export default function Hero() {
  return (
    <Canvas
      frameloop="demand"
      camera={{ position: [0, 0, 4], fov: 45 }}
      dpr={[1, 2]}
      gl={{ antialias: true, powerPreference: "high-performance" }}
    >
      <ambientLight intensity={0.4} />
      <directionalLight position={[3, 4, 5]} intensity={1.2} />
      <HeroObject />
      <Environment preset="city" />
    </Canvas>
  );
}

dpr={[1, 2]} caps the device pixel ratio at 2, the single biggest lever against overdraw on Retina and 4K displays. Reading prefers-reduced-motion before touching rotation means the hero is calm for users who ask for calm — required behavior, not a nicety. Expected behavior: one purple icosahedron rotating gently, or sitting still for reduced-motion users. Trade-off: frameloop="demand" gives you the lowest idle cost, but you must remember to invalidate() for continuous motion; forget it and the object freezes after one frame.

Demo 2 — The DOM overlay markup. The 3D means nothing without the text in front of it. This is the layer crawlers and screen readers actually read.

export function HeroOverlay() {
  return (
    <div className="hero__overlay">
      <h1>Cinematic 3D, shipped in a day.</h1>
      <p>Own-for-life Three.js templates and components.</p>
      <a className="hero__cta" href="/pricing/">Get AETumi</a>
    </div>
  );
}

The overlay is ordinary, semantic HTML — one <h1>, one paragraph, one real link. Expected behavior: the headline is selectable, indexable, and reachable by keyboard even if WebGL never initializes. Trade-off: you must manage pointer-events in CSS (none on the wrapper, auto on the CTA) so the object stays interactive behind non-interactive text.

Demo 3 — A vanilla Three.js r160 hero with ES modules. No React required. Same discipline: one renderer, one object, capped pixel ratio, a loop you can stop.

import * as THREE from "three";

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
document.querySelector(".hero").appendChild(renderer.domElement);

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 100);
camera.position.z = 4;

const mesh = new THREE.Mesh(
  new THREE.IcosahedronGeometry(1.2, 0),
  new THREE.MeshStandardMaterial({ color: "#8a5cff", metalness: 0.6, roughness: 0.25 })
);
scene.add(mesh, new THREE.DirectionalLight(0xffffff, 1.2));

renderer.setAnimationLoop(() => {
  mesh.rotation.y += 0.01;
  renderer.render(scene, camera);
});

Load three from a pinned CDN via an import map (Three.js r160 ships as ES modules, not a UMD global). Expected behavior: the same rotating object without a framework. Trade-off: you own resource cleanup and resize handling yourself — power in exchange for boilerplate.

Demo 4 — Pause the loop off-screen and on tab-hide. A hero that keeps rendering after the user scrolls away is a battery leak.

const io = new IntersectionObserver(([entry]) => {
  renderer.setAnimationLoop(entry.isIntersecting ? tick : null);
});
io.observe(document.querySelector(".hero"));

document.addEventListener("visibilitychange", () => {
  if (document.hidden) renderer.setAnimationLoop(null);
});

function tick() {
  mesh.rotation.y += 0.01;
  renderer.render(scene, camera);
}

Expected behavior: the loop stops the moment the hero leaves the viewport or the tab is backgrounded, and resumes on return. Trade-off: you must re-arm the loop on re-entry (the IntersectionObserver callback does that here); miss it and the hero stays frozen after the first scroll-away.

Demo 5 — A responsive resize handler. Heroes are full-viewport, so they must survive rotation and window resizing.

addEventListener("resize", () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});

Expected behavior: no stretching or blur when the viewport changes. Trade-off: resize fires rapidly during drag; for heavy scenes, debounce it so you are not reallocating buffers dozens of times a second.

Demo 6 — Dispose everything on teardown (React or SPA navigation). In single-page apps, forgetting this stacks render loops and leaks GPU memory across route changes.

function disposeHero() {
  renderer.setAnimationLoop(null);
  mesh.geometry.dispose();
  mesh.material.dispose();
  renderer.dispose();
  renderer.domElement.remove();
}

Expected behavior: a clean unmount with no lingering context. Trade-off: you must call it on every exit path (route change, component unmount, error boundary) — a single missed path reintroduces the leak.

Real product evidence

The demo video the generator places here shows a hero in motion: a single reflective object over a graded background, a headline reading in front of it, and the loop settling to idle when the scroll passes it. What it proves is the point of this article — the "cinematic" impression comes from one well-lit object plus an environment map, not from piling on geometry. Watch the reflections as the object turns; that HDRI-driven material response does more visual work than any particle system would, at a fraction of the frame cost.

Performance: the do/don't checklist

A 3D hero lives or dies on the first paint and the frame budget. Use this as a pre-ship gate.

ConcernDoDon't
Pixel ratioCap at Math.min(devicePixelRatio, 2)Render at full 3x DPR on mobile
GeometryOne hero object, low-to-mid polyLoad a 2M-triangle sculpt for a background
LoopPause when off-screen or tab hiddenRun requestAnimationFrame forever
AssetsCompress textures, use .glb with DracoShip raw 8K PNGs and uncompressed models
Lighting1–2 lights + an environment mapSix real-time shadow-casting lights
MotionHonor prefers-reduced-motionForce spin and parallax on everyone
FallbackStatic poster image until WebGL is readyBlank screen while the scene loads

Beyond the table: lazy-init the scene so the canvas mounts only near the viewport, showing a poster until the first frame is ready; budget the model by baking detail into normal maps instead of geometry; and never animate layout — rotate inside WebGL, but never animate CSS width/height per frame, which forces reflow and torches your INP.

Accessibility and SEO for 3D heroes

Because the headline is real HTML, crawlers and screen readers get the message even if WebGL never initializes. Add aria-hidden="true" to the canvas — it is decorative — and make sure the CTA is a real link or button in the tab order. Provide a static poster via a CSS background-image on the hero so the section is never empty on slow connections or unsupported devices.

This is why the layered approach beats a full-screen video or a canvas-only hero: you keep semantic HTML and Core Web Vitals under control while still getting the cinematic surface. The WebGL rendering never carries meaning, so it never has to be crawlable — the DOM in front of it already is. If your hero conveys product information visually, mirror it in the text overlay; do not trap it in pixels.

Production trade-offs

Be honest about what a 3D hero costs: a WebGL dependency (Three.js is a real bundle), a build step for compressing models, and a maintenance surface a static image does not have. On low-end Android, even a well-tuned scene competes for the same GPU that renders the rest of the page. So use the smallest technology that achieves the impression you need — a tasteful CSS gradient with a single floating PNG can out-convert a heavy 3D scene on a slow connection. Reach for WebGL when motion and material are the message, not when you just want the page to look busy.

When to use a 3D hero

SituationWhy a 3D hero fits
Product or hardware launchMaterial, scale, and rotation sell better than a photo
Design/dev agency or studio siteThe hero itself is a portfolio piece proving capability
Configurable or interactive productThe hero previews the scene the product page expands
Brand that competes on craftLive reflections and motion signal premium positioning

When NOT to use a 3D hero

SituationBetter choice
Content/blog site where reading is the jobStatic image or CSS; keep LCP minimal
Audience skews low-end mobile / slow networksOptimized poster image or short looping video
Tight deadline with no 3D asset pipelineA designed hero image; add 3D later
The "3D" would be flat text on a planePlain HTML/CSS — WebGL adds cost, not value

Decision matrix

ApproachFirst-paint costInteractivityVisual ceilingMaintenanceBest for
Static imageLowestNoneMediumTrivialContent sites, fastest LCP
Looping videoMediumNoneHighLowCinematic mood, no interaction
CSS animationLowLowMediumLowSubtle motion, gradients, shapes
Three.js / R3F heroMedium–highHighHighestMediumProducts, studios, interactive brands

Read the matrix top to bottom: complexity and capability rise together. A Three.js hero section sits at the top for a reason — it is the only row that gives you real-time interactivity and the highest visual ceiling, and it is also the only row that can hurt your metrics if you skip the performance gate.

Expert Note — The fastest way to make a 3D hero feel cheap is to over-light it. Amateur scenes throw four or five bright lights at an object and flatten it; professionals use one key light, a soft fill, and an environment map (HDRI) to carry the reflections. The environment map does the heavy lifting on believable metal and glass — spend your effort choosing the right one, not adding lights, and your frame cost drops as your quality rises.

Expert Note — Ship the poster image first, always. The most common regression in 3D heroes is a blank frame while the model downloads and the WebGL context spins up. Render one still of the hero object, compress it, set it as the hero's CSS background-image, and mount the canvas only after the first frame is ready. Your LCP element becomes that poster — fast and predictable — and the live scene becomes pure enhancement.

Expert Note — Treat reduced-motion as a first-class layout, not an afterthought if statement. Users who set prefers-reduced-motion: reduce should get a composed, intentional still — the object at a flattering angle with good lighting — not a frozen mid-rotation pose. Design that resting state deliberately; a meaningful slice of your audience will only ever see it.

How AETumi approaches the 3D hero

AETumi is an AI-native 3D web platform that ships production-ready Three.js and WebGL heroes as editable source. Rather than generate a scene from scratch each time, its templates start from the three-layer model in this article — a capped-DPR canvas, a semantic DOM overlay, and a poster-first load — and the 3D components library defaults to the single-object-first aesthetic, so a new hero inherits the calm, premium look instead of drifting toward a particle-heavy tech demo.

Because every hero arrives as source you own, your AI coding assistant — Claude Code, Cursor, or Codex — can adapt an existing scene to your brand instead of hallucinating a new one. The AETumi MCP workflow, included with Full Stack, lets that assistant read the real component structure and edit it in place. The practical difference is correctness: the AI gets something valid to start from, which is exactly what generated 3D usually lacks. The stance is honest — AI accelerates the edit, but a human still reviews the material, the lighting, and the performance gate before ship.

GitHub and technical proof

The AETumiApp GitHub organization publishes runnable Three.js reference code that follows the patterns above — r160 loaded as ES modules through a pinned CDN import map (no UMD global), a client-boundary canvas, capped pixel ratio, and disposal on teardown. The nextjs-threejs-starter repo shows the canvas-as-island structure with a real HTML overlay and an SSR-safe boundary — the same layering a hero needs inside a Next.js page.

Two honest limitations before you clone: reference repos ship a representative hero object, not your brand asset, so you replace the geometry and material; and they assume you supply compressed .glb models and KTX2 textures through your own pipeline. The README performance notes mirror this article, so treat the code as a correct skeleton to extend, not a finished product to drop in unchanged.

What to prioritizeAETumi technical diagram — What to prioritizeRecommended priority weighting (higher = more important)Editable source you own90Accessibility built in85Themable tokens75Typed & documented70States handled80
What to prioritize

FAQ

Do I need React Three Fiber, or is plain Three.js fine for a hero? Plain Three.js is completely fine and often lighter. React Three Fiber shines when your site is already React or Next.js, because it manages the scene declaratively and cleans up on unmount. Both produce the same WebGL and follow the same rules — cap DPR, pause off-screen, honor reduced-motion. Choose R3F for ergonomics in a React app; choose vanilla Three.js for a standalone hero where you want direct control and the smallest dependency surface.

Will a Three.js hero section hurt my page speed? Not if you cap DPR, use one compressed asset, lazy-init the canvas, and show a poster first. The 3D should paint after your text, never before. The failure mode is loading a multi-megabyte model in front of first paint, which tanks LCP. Make the poster your LCP element and stream the live scene in behind it, and a 3D hero passes Core Web Vitals as cleanly as a static one.

How big should the hero model be? As small as looks good. For most heroes a compressed .glb under roughly 1–2 MB with baked normal maps is plenty — detail the silhouette, not the triangle count. A mid-poly object with a strong material and a good environment map reads as more premium than a raw high-poly sculpt that stalls the load, at a fraction of the bandwidth and GPU cost.

How do I make a Three.js hero work on mobile? Render at capped DPR, consider a simpler material, and lean harder on the reduced-motion and off-screen pauses. Test on a real mid-range Android device, not just a desktop throttle. On very low-end hardware, a static poster is a legitimate final answer — serving it below a performance threshold is a feature, not a failure.

Can I animate the hero on scroll instead of on a timer? Yes — tie the object's rotation or the camera to scroll progress instead of a clock. See our approach to 3D scroll for scrubbing a scene to the scrollbar. Scroll-driven motion feels more intentional than a constant spin, and it naturally pauses when the user stops scrolling, which helps your frame budget.

  • Three.js hub — the umbrella guide to building 3D web with Three.js.
  • 3D components — the single-object-first hero and scene components referenced here.
  • WebGL — the rendering layer under every hero, explained.
  • 3D scroll — scrub a hero or below-fold scene to the scrollbar.
  • AETumi pricing — Standard, Pro, Premium, and Full Stack, one payment, yours to keep.

Conclusion

A great Three.js hero section is disciplined: one canvas, one object, real HTML on top, a poster that paints first, and a render loop that knows when to stop. Nail the three-layer architecture and the performance gate, and you get a first screen that feels custom-built without dragging down your metrics — cinematic on the surface, crawlable and accessible underneath.

If you would rather start from something proven than wire it all by hand, AETumi.app ships production-ready 3D components and full Three.js templates you own for life. Because every hero arrives as editable source, your AI assistant can adapt a scene to your brand instead of regenerating one from scratch. Browse the library at aetumi.app/pricing — Standard $19, Pro $39, Premium $99, and Full Stack $129 (full source plus the AETumi MCP), one payment, yours to keep.

More from the AETumi library

Real, production-ready assets — preview the motion, grab the source.

Browse all 3D components →