π§ββοΈ Your magic WebGL carpet
β οΈβ οΈ BETA! β οΈβ οΈ (Most likely I won't maintain this...)
Aladino is a tiny (around ~5kb gzipped) and dependency-free javascript library that allows to enhance your site using "shader effects".
The library is using WebGL1 and has progressive enhancement and accessibility in mind.
Because examples are worth thousand of words: https://luruke.github.io/aladino/
It was developed during some R&D time at EPIC Agency, back in 2019, and it's currently used on:
CSS is cool and powerful, you can build complex responsive layouts and more. Unfortunately the creative interactions you can achieve are very limited (only basic transforms, basic set of CSS filters).
Following the footsteps of an old and depracated CSS spec (Custom filters aka CSS Shaders) this library allow to "augment" your DOM elements.
Aladino operates on a full-screen canvas as position: fixed
. You'll likely want this canvas to be as background or foreground of your site.
When any Element
is added to aladino, the original DOM element will be hidden (via opacity: 0
), and a WebGL plane with the exact same size and position will be created in the canvas.
At resize and page scroll, aladino will make sure the WebGL plane matches the position and size of your DOM element.
Instead of using an orthographic camera, a perspective one is used, so you can make fancy perspective effects easily.
The library itself is tiny and doesn't support many of the WebGL capabilities (stencil, cube maps, array uniforms...). The rendering approach is very tailored for this specific use case.
If you need to render 3D objects and do more complex things, you might want to use libraries like pixi, ogl or three.js and build the dom->gl layer yourself.
Some features:
OES_vertex_array_object
).The library should work on every browser supporting WebGL, eventually you might want to add a polyfill for OES_vertex_array_object.
For older browsers, you might need to transpile the code in order to support ES features like class, Map, destructuring
.
The library has three main concepts:
A very basic example:
import Aladino from "aladino";
const aladino = new Aladino();
document.body.appendChild(aladino.canvas);
aladino.carpet(document.querySelector(".element"), {
material: aladino.material({
vertex: `
attribute vec2 position;
attribute vec2 uv;
uniform mat4 projection;
uniform float time;
void main() {
vec4 p = vec4(position, 0.0, 1.0);
p.z += sin(uv.x * 3.0 + time * 0.003);
gl_Position = projection * p;
}
`,
fragment: `
precision highp float;
void main() {
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
`,
}),
});
Running this piece of code, will create a green animating box, that replace your .element
.
Arguments with the default values and methods.
const aladino = new Aladino({
// HTMLCanvasElement β The canvas to use
canvas: document.createElement("canvas"),
// Number β Pixel ratio to use (example for retina displays)
dpr: Math.min(devicePixelRatio, 2),
// Number β Define horizontal and vertical mesh density of the plane
density: 1,
// Boolean β Whether need to track the page scroll (`scroll` on `window`)
autoScroll: true,
// WebGLContextAttributes β An object for WebGL context attributes
attribs: { antialias: true },
// Object { fragment: String, uniforms: Object } - Enable postprocessing using a big-triangle
post: false,
});
// WebGLRenderingContext - The WebGL context used by aladino
aladino.gl;
// Number β Read/set the horizontal scrolling in px
aladino.x;
// Number β Read/set the vertical scrolling in px
aladino.y;
// Map - All carpets instances
aladino.carpets;
// Carpet - Get the carpet instance of a specific Element
aladino.carpets.get(document.querySelector(".element"));
// Force the resize
aladino.resize();
// Destroy the instance
aladino.destroy();
const material = aladino.material({
// String β Vertex shader
vertex: `
attribute vec2 position;
attribute vec2 uv;
uniform mat4 projection;
uniform vec2 size;
uniform float time;
void main() {
vec4 p = vec4(position, 0.0, 1.0);
gl_Position = projection * p;
}
`,
// String β Fragment shader
fragment: `
precision highp float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`,
// Object - Uniforms shared across all the carpets using this material
uniforms: {},
/*
{
enable: false, // uniform bool enable;
speed: 0.4, // uniform float speed;
scale: [1, 1.2], // uniform vec2 scale;
color: [1, 0, 0], // uniform vec3 color;
color2: [1, 0, 0, 1], // uniform vec4 color2;
tex: aladino.texture() // uniform sampler2D tex;
}
*/
});
Default attributes / uniforms:
attribute vec2 position;
attribute vec2 uv;
uniform mat4 projection;
uniform vec2 size; // Size in px of your carpet
uniform vec2 viewport;
uniform float time; // current time
// When using a `sampler2D` texture, it will automatically send the size in px of the texture
uniform vec2 size$SAMPLER_UNIFORM_NAME;
const carpet = aladino.carpet(Element, {
// Aladino Material β The material to use for this carpet
material,
// Boolean β Use gl.LINES
wireframe: false,
// Array[Number, Number] βΒ Offset in px [x, y]
position: [0, 0],
// Array[Number, Number] βΒ Scale multiplier [width, height]
scale: [1, 1],
// Number - order of depth of the carpet in the scene
order: 10,
// Uniforms β Uniforms specific to this carpet
uniforms: {},
});
// Force the resize of the carpet
// Note, aladino uses `ResizeObserver` API, so already tracks some element changes
carpet.resize();
// Destroy the carpet
carpet.destroy();
// Boolean βΒ Indicate if the carpet is active, so if needs to be drawn an updated
carpet.active;
After creating a carpet, the DOM Element will be set as opacity: 0
and a CSS class aladino
will be added.
// String β URL of the texture
const texture = aladino.texture(url, {
// Boolean - Apply anisotropy texture filtering via `EXT_texture_filter_anisotropic`
anisotropy: false,
});
// Promise<Texture>
texture.loading;
Texture instances can be passed as regular uniforms.
If you encour in performance issues:
GL_OES_standard_derivatives
instead)Usually the biggest "price to pay" for this technique is the compositing phase between the canvas and the DOM due how browsers works internally.
The library automatically tracks your scroll and keep in sync the WebGL, but in case of frame dropping during scroll, you will notice the DOM to be "smoother", that's because the browser prioritise it's own rendering instead of Javascript tasks - consider to use a "virtual scrolling" if needed.
If you're using a custom font, makes sure to "resize" aladino after the font is loaded (as it might cause a layout shifting).
The library tracks the position of your DOM elements at page load and resize and with ResizeObserver
API if available. If you change the position of your item, you'll have to remember to use .resize()
method.
transform: translate()
?