Pocket Shader
A pocket-sized javascript library for easy WebGL shader rendering.
npx jsr add @braebo / pocket-shader
A pocket-sized javascript library for easy WebGL shader rendering.
npx jsr add @braebo / pocket-shader
By default, the PocketShader class creates a canvas and appends
it to the
body with css position: fixed.
import { PocketShader } from 'pocket-shader'
new PocketShader()
To use a specific container, pass an element or selector as the first argument.
import { PocketShader } from 'pocket-shader'
new PocketShader('#codeblock2')
The canvas will always grow to fill its container, remaining responsive and resizing automatically.
Shaders can be passed into the fragment and/or
vertex options.
import { PocketShader } from 'pocket-shader'
const ps = new PocketShader('#codeblock3', {
fragment: `
uniform float u_time;
uniform vec2 u_mouse;
in vec2 vUv;
out vec4 color;
void main() {
vec2 p = vUv - 0.5, m = u_mouse - 0.5;
float a = atan(p.y, p.x) - atan(m.y, m.x);
float c = 0.5 + 0.5 * cos(a);
color = vec4(c, m.x + 0.5 * c, m.y + 0.5 * c, 1.0);
}
`
})
The default vertex and fragment shaders:
in vec4 a_position;
out vec2 vUv;
void main() {
vUv = a_position.xy;
gl_Position = a_position;
}
#version 300 es
precision mediump float;
in vec2 vUv;
out vec4 color;
uniform float u_time;
void main() {
color = vec4(vUv, 0.5 + 0.5 * sin(u_time), 1.0);
} note: #version and precision are automatically injected
(if missing).
Bundlers like vite make it easy to import your shaders directly from
.glsl files.
import { PocketShader } from 'pocket-shader'
import fragment from './clouds.glsl?raw'
new PocketShader('#codeblock4', {
autoStart: true,
fragment
})
The autoStart option will start a render loop as soon as the canvas has
been appended to its container.
import { PocketShader } from 'pocket-shader'
import fragment from './bruh.frag?raw'
new PocketShader('#codeblock5', {
fragment,
autoStart: true
})
The on method allows you to listen for the 'render' event,
which fires before each frame.
import { PocketShader } from 'pocket-shader'
import fragment from './kirby.frag?raw'
const ps = new PocketShader('#codeblock11', {
fragment,
autoStart: true,
uniforms: {
u_jump: { type: 'float', value: 0 }
},
})
ps.on('render', ({ time }) => {
ps.uniforms.u_jump.value = Math.sin(time)
})
The animation loop can be controlled with methods like
start()
and stop().
import { PocketShader } from 'pocket-shader'
const ps = new PocketShader('#codeblock6')
ps.start()
ps
.
stateps
.
time
The speed can be passed as an option, or adjusted freely on each instance.
import { PocketShader } from 'pocket-shader'
new PocketShader('#codeblock7', {
autoStart: true,
speed: 2
})
2.00
The maxPixelRatio will limit the pixel ratio of the canvas, which is determined
by the device's pixel ratio by default.
Lowering this can increse performance significantly depending on the size of the canvas / DPI of the screen.
import myShader from './dyingUniverse.glsl?raw'
import { PocketShader } from 'pocket-shader'
new PocketShader('#codeblock8', {
fragment: myShader,
maxPixelRatio: 2
autoStart: true,
})
0.1
The following built-in uniforms are always available:
uniform vec2 u_resolution;
uniform float u_time;
uniform vec2 u_mouse;
in vec2 vUv;
u_resolution
The canvas resolution is determined by the container size and the
maxPixelRatio.
u_time
The time in seconds since the shader started. This can be controlled directly with
ps.time. See
Playback Controls.
u_mouse
The mouse position, normalized to vec2(0-1). See
Mouse Position.
The uniforms option accepts custom uniforms.
They can be accessed anytime with
ps.uniforms and will auto-update in the shader
when changed.
import { PocketShader } from 'pocket-shader'
import myShader from './glass.frag?raw'
const ps = new PocketShader('#codeblock9', {
fragment: myShader,
autoStart: true,
uniforms: {
octave: { type: 'int', value: 3 },
zoom: { type: 'float', value: 0 },
sphere: { type: 'float', value: 1 },
// etc...
}
})
// Update them later
ps.uniforms.octave.value = 4
ps.uniforms.zoom.value = 1
For now, uniforms must be {type,value} objects. In
the future, it will accept primitive values and infer the type internally.
(just ran out of time for now...)
The mouse position is available in the uniform u_mouse.
The position is normalized to vec2(0-1), where [0, 0] is the
bottom left corner of the canvas (bottom is 0 in GLSL).
const ps = new PocketShader({
fragment: `
uniform vec2 u_resolution;
uniform vec2 u_mouse;
in vec2 vUv;
out vec4 color;
void main() {
vec2 uv = vUv * u_resolution;
float size = u_resolution.x * 0.05;
vec2 mousePos = u_mouse * u_resolution;
float dist = length(uv - mousePos);
float circle = smoothstep(size, size - 0.1, dist);
float glowEffect = exp(-dist * 0.1);
color = vec4(vec3(circle * glowEffect), 1.0);
}
`,
})
It can be manually updated on the instance with ps.mouse.
/**
* Always keep the mouse position updated,
* even while outside the canvas bounds.
*/
window.addEventListener('mousemove', e => {
// normalized to 0-1
ps.setMousePosition(e)
// or, raw dogged
ps.mouse = {
x: e.clientX
y: e.clientY
}
})
mouseSmoothing can be passed in as an option, and/or updated on an instance.
It ranges from 0-1, where 0 is no smoothing, and 1 is infinite smoothing.
const ps = new PocketShader('#codeblock10', {
mouseSmoothing: 0.8,
fragment: `...`
})
ps.mouseSmoothing = 0.95
0.95