Three Js Shader example
How it works
A plane drawn by a GLSL fragment shader, with a panel for its uniforms. Seven shaders, one canvas.
The class in src/js/handler/three-shaders is the plain Three.js (three on npm) setup of the cube page with the mesh swapped for a 2×2 plane and a ShaderMaterial: a vertex shader shared by every variation, which passes the UV coordinates on, and a fragment shader chosen by name. Each variation is one file in variations/ with its GLSL, the initial values of its uniforms, and a function that adds the controls for them to a lil-gui panel, so a control changes a uniform and the next frame shows it.
The panel is appended inside the frame, top right. A shader that animates (reveal, with Motion on) declares needsTime and the loop advances its uTime uniform by 0.016 per frame. The material is transparent, so shaders that write an alpha below 1 let the page show through.
The markup
One element with the js--three-webgl-shader hook, a component class that gives it a height, and data-shader-type with the shader to start with. The select is optional: if the frame contains one with the js--three-webgl-shader-select hook, the handler passes it to the class and every change swaps the shader on the same canvas. The option values are the shader types; the markup of the select is the form select component.
<!-- The element is the frame; the class appends a canvas that fills it. data-shader-type picks the first shader -->
<div class="c--shader-a js--three-webgl-shader" data-shader-type="grid">
<!-- Optional: a select inside the frame switches the shader on the same canvas -->
<div class="c--shader-a__hd">
<div class="c--form-group-a">
<label class="c--label-a" for="shader-type">Shader</label>
<div class="c--form-select-a">
<select id="shader-type" class="c--form-select-a__item js--three-webgl-shader-select">
<option value="grid">Grid</option>
<option value="gradient">Gradient</option>
<option value="circle">Circle</option>
<option value="stripes">Stripes</option>
<option value="reveal">Reveal</option>
<option value="wiggle">Wiggle</option>
<option value="noise">Noise</option>
</select>
</div>
</div>
</div>
</div> The handler
Handler.js looks for the select inside the element and passes it along; null when there is none, and the class then draws the one shader of the attribute:
this.config = ({ element }) => ({
element,
selectElement: element.querySelector(".js--three-webgl-shader-select"),
}); The switch is setShader in ThreeGLShader.js. It disposes the current material and the panel, builds both again from the new variation and keeps the renderer, the scene and the canvas. The select calls it on change, and any other code can call it on the instance:
/**
* Switches the shader drawn on the plane. The old material and its GUI are disposed and
* rebuilt from the new variation; the renderer, scene and canvas stay.
* @param {String} type - A key of SHADERS: grid, gradient, circle, stripes, reveal, wiggle or noise
*/
setShader(type) {
if (!this.mesh || !SHADERS[type] || type === this.shaderType) return;
this.shaderType = type;
this.shaderConfig = SHADERS[type];
this.debugObject = { ...this.shaderConfig.debug };
this.DOM.element.dataset.shaderType = type;
if (this.material) this.material.dispose();
this.material = this._createMaterial();
this.mesh.material = this.material;
if (this.gui) this.gui.destroy();
this._setupGUI();
if (this.DOM.selectElement && this.DOM.selectElement.value !== type) {
this.DOM.selectElement.value = type;
}
} This is a variation. fragment is the GLSL, uniforms returns fresh uniform objects for every material, debug holds the values the panel edits and gui wires each control to its uniform:
export const gridShader = {
fragment: /* glsl */ `
varying vec2 vUv;
uniform vec3 uColor;
uniform float uGridSize;
float random(vec2 st) {
return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
}
void main() {
vec2 grid = floor(vUv * uGridSize);
float strength = random(grid);
gl_FragColor = vec4(uColor * strength, 1.0);
}
`,
uniforms: () => ({
uColor: { value: new THREE.Color("#e94560") },
uGridSize: { value: 10 }
}),
debug: {
color: "#e94560",
gridSize: 10
},
gui: (gui, debug, material) => {
gui.addColor(debug, "color").name("Color").onChange((v) => {
material.uniforms.uColor.value.set(v);
});
gui.add(debug, "gridSize", 2, 50, 1).name("Grid Size").onChange((v) => {
material.uniforms.uGridSize.value = v;
});
}
}; What the framework does for you
- Lazy loading. An element is only built when it gets within 30px of the viewport (Boostify scroll).
ThreeGLShader.js, withthree,lil-guiand the seven variations, is imported through the Manager on first use. - Page transitions. Before swup replaces the content every instance is destroyed: the frame loop is cancelled, the resize and select listeners removed, the panel destroyed, the geometry and material disposed, the scene cleared, the renderer disposed and its canvas taken out of the DOM.
- One renderer per instance. Every element gets its own
WebGLRendererand WebGL context. That is why this page has one canvas and a select rather than one canvas per shader: browsers allow a limited number of live contexts, and switching the material costs nothing.
Shader
One canvas. Pick a shader in the select; tune it in the panel.
shader-type="grid"js--three-webgl-shader-select Starts on grid, as the attribute says. Changing the select swaps the fragment shader and rebuilds the panel for its uniforms; the values of the previous shader are not kept, every variation starts from its defaults. The panel edits the uniforms of the material directly, so there is nothing to apply.
<!-- The element is the frame; the class appends a canvas that fills it. data-shader-type picks the first shader -->
<div class="c--shader-a js--three-webgl-shader" data-shader-type="grid">
<!-- Optional: a select inside the frame switches the shader on the same canvas -->
<div class="c--shader-a__hd">
<div class="c--form-group-a">
<label class="c--label-a" for="shader-type">Shader</label>
<div class="c--form-select-a">
<select id="shader-type" class="c--form-select-a__item js--three-webgl-shader-select">
<option value="grid">Grid</option>
<option value="gradient">Gradient</option>
<option value="circle">Circle</option>
<option value="stripes">Stripes</option>
<option value="reveal">Reveal</option>
<option value="wiggle">Wiggle</option>
<option value="noise">Noise</option>
</select>
</div>
</div>
</div>
</div> shader-type="grid"js--three-webgl-shader-select Options reference
One attribute, one optional hook, and seven shaders with their panel controls.
HTML
| Attribute or hook | Values | What it does |
|---|---|---|
data-shader-type | one of the types below, default grid | The shader drawn first. A missing or unknown value falls back to grid. The class writes the current type back to it on every switch |
js--three-webgl-shader-select | a <select> inside the frame, optional | Switches the shader on change; every option value is a type. The class sets it to the current type when it starts |
Shaders
| Type | What it draws | Panel controls |
|---|---|---|
grid | Random grid pattern | Color, Grid Size |
gradient | Vertical gradient between two colors | Color A, Color B |
circle | A circle cut out of a plane | Color, Radius, Opacity |
stripes | Vertical stripes | Color, Frequency |
reveal | Perlin noise with a directional wipe and a progress control; animated when Motion is on | Color, Scale, Progress, Feather, Direction, Dir. Strength, Motion, Motion Speed |
wiggle | A wavy ring | Color, Frequency, Amplitude, Opacity |
noise | Perlin noise pattern | Color, Scale, Opacity |
Class
| In the class | Value | What it does |
|---|---|---|
setShader(type) | method | Switches the shader from code. Ignores an unknown type and the current one |
PerspectiveCamera(75, …), position.z = 2 | fov 75 | With the 2×2 plane, the plane fills the height of the frame |
uTime += 0.016 | per frame | The speed of animated shaders, in animate |
SHADERS in variations/index.js | map of type to variation | Add a file to variations/ and an entry here to add a shader |
The size of the canvas is the size of the element. c--shader-a is one example of sizing, with its __hd element placing the select over the canvas; js--three-webgl-shader and js--three-webgl-shader-select are hooks and carry no styles.