regl 5,583 (MIT, 2.1.1, github.com/regl-project/regl (https://github.com/regl-project/regl 5,583 ), about 5,600 stars) replaces WebGL's global state machine with commands: one object declares the shaders, attributes, uniforms and state of a draw, and calling it draws. regl.prop() marks values supplied per call, and passing an array draws once per element. regl tracks what is bound, so it skips redundant state changes for you:
<canvas id="c" width="1200" height="300" style="width:600px;height:150px"></canvas>
<script src="https://cdn.jsdelivr.net/npm/regl@2.1.1/dist/regl.min.js"></script>
<script src="webgl-helper.js"></script>
<script>
const regl = createREGL({ canvas: document.getElementById('c') });
const drawCover = regl({ // shaders, attributes and state in one object
vert: `precision mediump float;
attribute vec2 corner; uniform vec2 offset; uniform float tilt; varying vec2 uv;
void main() { uv = corner;
mat2 turn = mat2(cos(tilt), sin(tilt), -sin(tilt), cos(tilt));
vec2 p = turn * ((corner - 0.5) * vec2(1.04, 1.5)); // size, then turn
gl_Position = vec4(p.x / 4.0 + offset.x, p.y + offset.y, 0, 1); }`, // aspect 4:1
frag: `precision mediump float; uniform vec3 color; varying vec2 uv;
void main() { gl_FragColor = vec4(color * mix(0.55, 1.0, step(0.08, uv.x)), 1); }`,
attributes: { corner: [[0, 0], [1, 0], [0, 1], [1, 1]] },
uniforms: { offset: regl.prop('offset'), color: regl.prop('color'),
tilt: regl.prop('tilt') },
primitive: 'triangle strip', count: 4,
});
regl.clear({ color: [0.93, 0.91, 0.87, 1] });
drawCover(GLH.covers.map((hex, i) => // one draw per props object
({ offset: [-0.8 + i * 0.32, 0], color: GLH.rgb(hex), tilt: (i - 2.5) * 0.08 })));
window.__done = true;
</script>
regl created a WebGL 1 context here (regl._gl instanceof WebGL2RenderingContext was false), so WebGL 2 features are out of reach; its last release dates from November 2024. Its command model resembles WebGPU's pipelines, a gentle step toward WebGPU.
<!doctype html>
<style>
body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
p { max-width: 600px; margin: 4px 6px; font: 11px monospace; }
</style>
<canvas id="c" width="1200" height="300"></canvas>
<p id="out"></p>
<script src="https://cdn.jsdelivr.net/npm/regl@2.1.1/dist/regl.min.js"></script>
<script>
const covers = [[0.12, 0.37, 0.55], [0.36, 0.25, 0.6], [0.88, 0.6, 0.06], [0.25, 0.49, 0.23], [0.71, 0.27, 0.18], [0.16, 0.62, 0.56]];
const regl = createREGL({ canvas: document.getElementById('c') });
const drawCover = regl({ // shaders, attributes and state in one object
vert: `precision mediump float;
attribute vec2 corner; uniform vec2 offset; uniform float tilt; varying vec2 uv;
void main() { uv = corner;
mat2 turn = mat2(cos(tilt), sin(tilt), -sin(tilt), cos(tilt));
vec2 p = turn * ((corner - 0.5) * vec2(1.04, 1.5)); // size, then turn
gl_Position = vec4(p.x / 4.0 + offset.x, p.y + offset.y, 0, 1); }`, // aspect 4:1
frag: `precision mediump float; uniform vec3 color; varying vec2 uv;
void main() {
float spine = step(0.08, uv.x); // darker spine strip
float band = 1.0 - 0.25 * step(0.62, uv.y) * step(uv.y, 0.72); // a title band
gl_FragColor = vec4(color * mix(0.55, 1.0, spine) * band, 1); }`,
attributes: { corner: [[0, 0], [1, 0], [0, 1], [1, 1]] },
uniforms: { offset: regl.prop('offset'), color: regl.prop('color'), tilt: regl.prop('tilt') },
primitive: 'triangle strip', count: 4,
});
regl.clear({ color: [0.93, 0.91, 0.87, 1] });
drawCover(covers.map((color, i) => // an array of props: one draw per element
({ offset: [-0.8 + i * 0.32, 0], color, tilt: (i - 2.5) * 0.08 })));
document.getElementById('out').textContent =
`regl ${regl.version ?? ''} | context: ${regl._gl instanceof WebGL2RenderingContext ? 'WebGL 2' : 'WebGL 1'} | 1 command, 6 props objects`;
</script>