VFX-JS is a JavaScript library to add WebGL-powered effects to
your website.
You can easily attach it to normal
<img>, <video> elements
etc.
Install via npm:
npm i @vfx-js/core
Then create VFX object in your script:
import { VFX } from '@vfx-js/core';
const img = document.querySelector('#img');
const vfx = new VFX();
vfx.add(img, { shader: "glitch", overflow: 100 });
This will be rendered as follows:
VFX-JS is also available on CDNs (esm.sh or jsDeliver). So you can use VFX-JS in CodePen etc:
// Load VFX-JS from esm.sh
import { VFX } from "https://esm.sh/@vfx-js/core";
// or jsDeliver
// import { VFX } from 'https://cdn.jsdelivr.net/npm/@vfx-js/core/+esm'
// Then use VFX-JS
const vfx = new VFX();
vfx.add(img { shader: 'rgbShift' });
Image
<img src="example.png" />
vfx.add(img, { shader: "rgbShift" });
Output
GIF
<img src="example.gif" />
vfx.add(gif, { shader: "rainbow" });
Output
Video
<video src="example.mp4" autoplay loop muted/>
vfx.add(video, { shader: "halftone" });
Output:
Div (experimental)
<div id="div">
<p>You can interact with these inputs.</p>
<input type="text" value="Edit me" />
<input type="range" min="0" max="100" value="0" />
<textarea>Edit me</textarea>
</div>
vfx.add(div, { shader: "rgbShift", overflow: 100 });
// Update on input
input.addEventListener('input', () => vfx.update(div));
// Update on textarea resize
const mo = new MutationObserver(() => vfx.update(div));
mo.observe(textarea, { attributes: true });
Output:
You can interact with these inputs.
Canvas
<!--
VFX-JS also supports HTMLCanvasElement as the input.
You can draw 2D graphics and text in canvas,
then pass it to VFX-JS to add post effects.
-->
<canvas id="canvas"/>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function drawCanvas() {
...
// Update texture when the canvas has been updated
vfx.update(canvas);
requestAnimationFrame(drawCanvas);
}
drawCanvas();
vfx.add(canvas, { shader });
Output:
vfx.add(el, { shader: "rainbow" });
vfx.add(el, { shader: "rgbShift" });
// Some shaders require "overflow" property
// so that they can render beyond the original area.
//
// In this example, the "glitch" shader can render
// outside the original element up to 100px.
vfx.add(el, { shader: "glitch", overflow: 100 });
Some shaders take parameters (e.g. duotone). You can pass
the params to the uniforms property.
vfx.add(el, {
shader: "duotone",
uniforms: {
color1: [0, 0, 1, 1],
color2: [0, 1, 0, 1],
speed: 0.2
}
});
VFX-JS presets for transition animation. These animation start when the element gets in the viewport.
vfx.add(el, { shader: "slitScanTransition" });
vfx.add(el, { shader: "warpTransition" });
vfx.add(el, { shader: "pixelateTransition" });
vfx.add(el, { shader: "focusTransition" });
You can write GLSL shader by yourself.
const shader = `
precision highp float;
uniform vec2 resolution;
uniform vec2 offset;
uniform float time;
uniform sampler2D src;
uniform float scroll;
out vec4 outColor;
void main (void) {
vec2 uv = (gl_FragCoord.xy - offset) / resolution;
// Scroll X
uv.x = fract(uv.x + scroll + time * 0.2);
outColor = texture2D(src, uv);
}
`;
vfx.add(el, {
shader,
uniforms: {
// Uniform functions are evaluated every frame
scroll: () => window.scrollY / window.innerHeight,
}
});
@vfx-js/effects ships ready-made
Effect classes — bloom, halftone, pixelate,
scanline, fluid, particles, voronoi. Each owns its own
params; mutate effect.params (or call
effect.setParams) to drive them live.
npm i @vfx-js/core @vfx-js/effects
import { VFX } from "@vfx-js/core";
import { BloomEffect } from "@vfx-js/effects";
const vfx = new VFX();
const effect = new BloomEffect({
threshold: 0.2,
intensity: 5,
});
await vfx.add(img, { effect });
Chain effects by passing an array — each runs in order, the
next reads the previous output as src.
import {
BloomEffect,
PixelateEffect,
ScanlineEffect,
} from "@vfx-js/effects";
await vfx.add(img, {
effect: [
new PixelateEffect({ size: 10 }),
new ScanlineEffect({ spacing: 5 }),
new BloomEffect({
threshold: 0.01,
intensity: 10,
pad: 200,
}),
],
});
See the effects README for the full effect list, or the Storybook for interactive demos.
You can chain multiple shader passes by passing an array
of VFXPass objects. Each pass writes to a
named buffer that subsequent passes can read.
// Pass 1: Blur the image into "blur" buffer
const blurPass = {
frag: `
precision highp float;
uniform sampler2D src;
uniform vec2 resolution;
uniform vec2 offset;
out vec4 outColor;
void main() {
vec2 uv = (gl_FragCoord.xy - offset)
/ resolution;
vec2 t = 4.0 / resolution;
vec4 c = texture(src, uv) * 0.4;
c += texture(src, uv+vec2(t.x,0))*.15;
c += texture(src, uv-vec2(t.x,0))*.15;
c += texture(src, uv+vec2(0,t.y))*.15;
c += texture(src, uv-vec2(0,t.y))*.15;
outColor = c;
}
`,
target: "blur",
};
// Pass 2: Combine original + blurred for glow
const glowPass = {
frag: `
precision highp float;
uniform sampler2D src;
uniform sampler2D blur;
uniform vec2 resolution;
uniform vec2 offset;
out vec4 outColor;
void main() {
vec2 uv = (gl_FragCoord.xy - offset)
/ resolution;
vec4 c = texture(src, uv);
vec4 b = texture(blur, uv);
outColor = c + b * 0.6;
}
`,
};
vfx.add(el, {
shader: [blurPass, glowPass],
});