Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-hero-mesh

An animated mesh gradient hero background for React. A 3D ground plane is displaced by simplex noise and painted with a five-colour blend that slowly drifts, rendered with three.js. Drop it behind a headline and you get a soft, living landscape instead of a flat gradient.

react-hero-mesh preview

Features

  • Five-colour palette blended per-vertex with layered noise, so the colours melt into each other organically instead of banding.
  • Fully configurable: hill height, animation speed, wave frequency, and camera rotation are all props, and changing them never rebuilds the WebGL scene (uniforms are mutated in place).
  • Camera orbit: the cameraAngle and cameraPitch props rotate and tilt the whole landscape while the plane is automatically re-fitted to the camera's ground footprint, so the page never peeks through at any angle or aspect ratio.
  • Drag to orbit: set interactive and users can drag on the hero to orbit (horizontal) and tilt (vertical) the camera, with an onCameraChange callback to keep external controls in sync.
  • Wireframe mode: flip the wireframe prop to render the displaced plane as a mesh of lines.
  • Performance minded: single WebGL context, capped pixel ratio, low-power GPU hint, and the render loop pauses when the component is off-screen or the tab is hidden.
  • Respects prefers-reduced-motion: renders a single static frame for users who opt out of animation.
  • Resize-aware: a ResizeObserver keeps the canvas and camera in sync with the container.
  • Clean teardown: geometry, material, and the GL context are disposed on unmount.

Installation

npm install react-hero-mesh three

react (18+) and three (0.152+) are peer dependencies (0.152 introduced the colour-space API the shader colours rely on).

Prefer to own the code? The whole component is a single file: copy src/index.tsx into your project and install three.

Usage

The component fills its nearest positioned ancestor (it renders position: absolute; inset: 0), so give the hero container position: relative and put your content above it:

import { HeroMesh } from "react-hero-mesh";

export function Hero() {
  return (
    <section style={{ position: "relative", overflow: "hidden", borderRadius: 24 }}>
      <HeroMesh
        colors={["#E3FBEF", "#C9F0D6", "#EAF7C7", "#FCE7D6", "#EFE7FF"]}
        amount={0.2}
        speed={0.02}
        frequency={3}
        cameraAngle={0}
      />
      <div style={{ position: "relative", zIndex: 1, padding: "6rem 2rem", textAlign: "center" }}>
        <h1>How can we help?</h1>
      </div>
    </section>
  );
}

All props are optional; with no props you get the default "Meadow" palette.

Next.js

The component is client-only (it needs a WebGL context) and ships with a "use client" directive. Because three.js is a heavy chunk, defer it:

import dynamic from "next/dynamic";

const HeroMesh = dynamic(() => import("react-hero-mesh"), { ssr: false });

Props

Prop Type Default Range Description
colors [string, string, string, string, string] ["#E3FBEF", "#C9F0D6", "#EAF7C7", "#FCE7D6", "#EFE7FF"] hex Five hex colours. The fifth is the base coat; the first four are layered over it with noise.
amount number 0.2 0 to 1 Hill height. 0 is a flat sheet, 1 is pronounced rolling dunes.
speed number 0.02 0 to 2 How fast the terrain and colours evolve. Even 0 keeps a very slow drift.
frequency number 3 0.2 to 8 Visible wave count. Low values give broad swells, high values give choppy ripples.
cameraAngle number 0 0 to 360 Orbit azimuth in degrees. Rotates the whole landscape in view.
cameraPitch number 42 ~25 to 85 Downward camera tilt in degrees. Clamped at runtime to the shallowest tilt that still lets the plane cover the container at the current aspect ratio (wider containers need steeper minimum tilts).
wireframe boolean false Render the displaced plane as a wireframe instead of filled colour.
interactive boolean false Let users drag on the container to orbit (horizontal) and tilt (vertical) the camera.
onCameraChange (camera: { angle: number; pitch: number }) => void Fires as the user drags, with the resulting camera angles. Use it to keep sliders or other controls in sync.
className string Extra class for the wrapper div.
style CSSProperties Extra styles for the wrapper div, merged over the absolute-fill defaults.

The stated ranges are the values the visuals were tuned for, not hard limits.

Presets

HERO_MESH_PRESETS exports five ready-made palettes (meadow, sunrise, lagoon, ember, forest), and HERO_MESH_DEFAULTS holds the default values for every prop:

import { HeroMesh, HERO_MESH_PRESETS } from "react-hero-mesh";

const forest = HERO_MESH_PRESETS.find((p) => p.id === "forest")!;

<HeroMesh colors={forest.colors} />;

Demo

An interactive playground lives in demo/: colour pickers with hex inputs, sliders for every prop, presets, randomise, wireframe, and drag-to-orbit on the hero itself. "Copy link" serialises the whole configuration into the URL so a look can be shared (e.g. /?preset=forest&angle=120&pitch=60).

cd demo
npm install
npm run dev

How it works

A unit plane (240x240 segments) is laid flat and displaced upward in the vertex shader by 3D simplex noise, with time as the third dimension so the terrain rolls. The same shader blends the five colours per-vertex: the base colour is progressively mixed toward each of the other four using smoothstep-shaped noise at slightly different flow speeds and seeds, which is what produces the soft, marbled patches. The fragment shader is flat (no lighting); the entire look comes from the colour blend plus the displaced form.

The camera orbits the plane by cameraAngle at a downward tilt of cameraPitch. After every camera or viewport change, the plane is rescaled and repositioned to cover the camera's ground footprint (with a margin), sized to the footprint's bounding circle so the framing stays constant while orbiting. Because UV space maps to that footprint, frequency reads as a stable "waves on screen" count at any container size. The tilt has a dynamic floor: below a certain pitch (which depends on the field of view and aspect ratio) the top of the frame would rise above the horizon and no ground plane could cover it, so the pitch is clamped just above that point.

Credits

The GLSL simplex noise is webgl-noise by Ashima Arts (MIT).

License

MIT

About

Animated 3D mesh gradient hero for React. A noise-displaced terrain with a five-colour blend and drag-to-orbit camera, rendered with three.js.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages