_❯Mesh Gradient

An animated mesh-gradient background: three drifting colour blobs, a vignette and optional film grain, all in one fragment shader.

WebGLGLSLgradientblobsvignette
1import { useState } from "react";
2import { MeshBackgroundCanvas } from "@/components/misc/mesh-background/mesh-background-canvas.component";
3import { LabsDemoLayout } from "../../components/labs-experiment-frame/labs-experiment-frame.component";
4import {
5 ButtonGroupControl,
6 ControlGroup,
7 ControlPanel,
8 ResetButton,
9 SliderControl,
10} from "../../components/labs-control/labs-control.component";
11import * as styles from "./mesh-background.demo.css";
12
13const STATES = ["paused", "running"] as const;
14type PlayState = (typeof STATES)[number];
15
16const DEFAULT_QUALITY = 0.45;
17
18export function MeshBackgroundDemo() {
19 const [quality, setQuality] = useState(DEFAULT_QUALITY);
20 const [state, setState] = useState<PlayState>("running");
21
22 const reset = () => {
23 setQuality(DEFAULT_QUALITY);
24 setState("running");
25 };
26
27 return (
28 <LabsDemoLayout
29 stage={
30 <div className={styles.stageInner}>
31 <div className={styles.meshBox}>
32 <MeshBackgroundCanvas quality={quality} animate={state === "running"} />
33 </div>
34 </div>
35 }
36 controls={
37 <ControlPanel>
38 <ControlGroup title="Animation">
39 <ButtonGroupControl
40 options={STATES}
41 value={state}
42 onChange={setState}
43 formatOption={(option) => option[0].toUpperCase() + option.slice(1)}
44 />
45 </ControlGroup>
46
47 <ControlGroup title="Film grain">
48 <SliderControl
49 label="Quality"
50 value={quality}
51 min={0}
52 max={0.75}
53 step={0.05}
54 onChange={setQuality}
55 format={(value) => value.toFixed(2)}
56 />
57 </ControlGroup>
58
59 <ResetButton onReset={reset} />
60 </ControlPanel>
61 }
62 />
63 );
64}