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
.
state
ps
.
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