npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@frapx/shader

v0.6.0

Published

Lightweight WebGL shader background runtime for websites.

Readme

@frapx/shader

npm version

Lightweight WebGL shader background runtime for websites.

This is not a scene, camera, mesh, or full rendering-engine abstraction. It creates and manages a WebGL canvas for an existing DOM region, then lets you drive fragment shaders with built-in uniforms, custom uniforms, textures, and optional previous-frame feedback.

Install

pnpm add @frapx/shader

Basic Usage

import { createShaderBackground, glsl } from "@frapx/shader";

const fx = createShaderBackground({
  target: ".hero",
  fragment: glsl`
    precision highp float;

    uniform vec2 u_resolution;
    uniform vec2 u_pointerUv;
    uniform float u_time;

    void main() {
      vec2 uv = gl_FragCoord.xy / u_resolution;
      float glow = 0.5 + 0.5 * sin(u_time + uv.x * 8.0);
      gl_FragColor = vec4(uv.x, u_pointerUv.y, glow, 1.0);
    }
  `
});

JS uniform names omit u_; GLSL uniforms use u_.

fx.setUniform("progress", 0.4);
// GLSL: uniform float u_progress;

This naming rule also applies to initial custom uniforms:

createShaderBackground({
  target: ".hero",
  fragment,
  uniforms: {
    progress: 0
  }
});

The shader must declare uniform float u_progress;, not uniform float progress;. Uniforms set before ready are cached and applied on the first render.

Examples

  • examples/vite-basic - minimal Vite usage and integration checks.
  • examples/vite-feedback - interactive previous-frame feedback demo.
  • examples/vite-react - React binding example.

Color Uniforms

Use hexToRgb() and hexToRgba() to convert hex colors into vec3 and vec4 uniform values.

import { createShaderBackground, glsl, hexToRgb, hexToRgba } from "@frapx/shader";

const fx = createShaderBackground({
  target: ".hero",
  fragment,
  uniforms: {
    baseColor: hexToRgb("#7dd3fc"),
    overlayColor: hexToRgba("#0f172acc")
  }
});
uniform vec3 u_baseColor;
uniform vec4 u_overlayColor;

The helpers support #rgb, #rgba, #rrggbb, #rrggbbaa, and the same forms without #. The returned values are sRGB channels normalized to 0..1. Invalid hex values throw an Error.

Textures

const fx = createShaderBackground({
  target: ".hero",
  fragment,
  textures: {
    image: "/hero.webp",
    mask: {
      source: "/mask.webp",
      wrap: "clamp",
      filter: "linear",
      flipY: true
    }
  }
});

Each texture creates a sampler and size uniform:

uniform sampler2D u_image;
uniform vec2 u_imageSize;

Supported v1 sources are image URL, HTMLImageElement, and HTMLCanvasElement.

Textures can be updated at runtime. Updates are async because URL sources must load before they can be uploaded to WebGL.

await fx.setTexture("image", "/next-hero.webp");

await fx.setTextures({
  image: "/next-hero.webp",
  mask: nextMaskCanvas
});

setTextures() is a partial update: omitted texture names are left unchanged. If a runtime texture update fails, the previous texture remains active and the returned promise rejects. In "demand" render mode, successful texture updates request a render.

Feedback

Enable feedback when a shader needs to read the previous rendered frame:

createShaderBackground({
  target: ".hero",
  fragment,
  feedback: true
});

This creates a managed ping-pong framebuffer pair and exposes the previous rendered frame as a sampler:

uniform vec2 u_resolution;
uniform sampler2D u_previousFrame;
uniform vec2 u_previousFrameSize;

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;
  vec4 history = texture2D(u_previousFrame, uv);
  gl_FragColor = mix(vec4(0.0), history, 0.96);
}

For GLSL ES 3.00 shaders, use the same uniform names with texture():

#version 300 es
precision highp float;

out vec4 fragColor;
uniform vec2 u_resolution;
uniform sampler2D u_previousFrame;
uniform vec2 u_previousFrameSize;

void main() {
  fragColor = texture(u_previousFrame, gl_FragCoord.xy / u_resolution);
}

feedback: true uses u_previousFrame and u_previousFrameSize. To customize the generated suffix name:

feedback: {
  uniform: "history",
  filter: "linear",
  wrap: "clamp",
  clearColor: [0, 0, 0, 0]
}

The suffix must be a valid GLSL identifier without the u_ prefix. For example, uniform: "history" creates u_history and u_historySize. A user texture with the same name is rejected.

The feedback texture always follows the canvas drawing-buffer size, and u_previousFrameSize reports that size in pixels. The first frame and any resize reset the texture to clearColor (transparent black by default). Alpha is preserved from the fragment output; the texture represents shader output before DOM compositing.

In renderMode: "demand", u_previousFrame is the result of the previous demand render. Enabling feedback does not start a continuous render loop by itself.

When feedback is enabled, onBeforeRender and onAfterRender wrap the main shader draw into the internal framebuffer. The copy to the visible canvas happens after onAfterRender.

See examples/vite-feedback for a small interactive feedback demo.

External Uniforms

Scroll is intentionally not built in. Use any scroll or animation library and push values into uniforms.

const fx = createShaderBackground({
  target: ".hero",
  fragment,
  uniforms: {
    progress: 0,
    velocity: 0
  },
  renderMode: "demand"
});

window.addEventListener("scroll", () => {
  const max = document.documentElement.scrollHeight - innerHeight;
  fx.setUniform("progress", max > 0 ? scrollY / max : 0);
});

Render Modes

createShaderBackground({
  target: ".hero",
  fragment,
  renderMode: "always" // default
});

"demand" renders when uniforms, pointer state, texture load, or resize changes. fx.render() is also available.

Built-In Uniforms

uniform vec2 u_resolution;     // drawing buffer px
uniform vec2 u_viewportSize;   // CSS px
uniform float u_pixelRatio;
uniform float u_time;          // seconds, paused offscreen/hidden
uniform float u_delta;         // seconds, clamped to 0.1
uniform vec2 u_pointer;        // drawing buffer px, bottom-left origin
uniform vec2 u_pointerUv;      // 0..1, bottom-left origin
uniform float u_pointerActive; // 0 or 1
uniform float u_reducedMotion; // 1 when the OS prefers reduced motion, else 0

u_reducedMotion is always supplied. Use it to soften or stop motion yourself when you do not want the library to pause the loop (see respectReducedMotion below).

Options

createShaderBackground({
  target: ".hero",
  canvas: existingCanvas,
  fragment,
  vertex,
  uniforms,
  textures,
  feedback: false,
  layer: "background",
  autoStart: true,
  pauseWhenOffscreen: true,
  pauseWhenHidden: true,
  respectReducedMotion: false,
  renderMode: "always",
  dpr: "auto",
  maxDpr: 2,
  autoResize: true,
  debug: false,
  canvasClass: "hero-fx",
  canvasStyle: {
    opacity: "0.8",
    mixBlendMode: "screen"
  },
  onReady(instance) {},
  onError(error) {},
  onBeforeRender(state) {},
  onAfterRender(state) {}
});

layer: "background" inserts the canvas as the first child with z-index: 0. layer: "overlay" inserts it as the last child with z-index: 1. Existing child styles are not changed.

Lifecycle & accessibility

The render loop is paused whenever it is not worth running, and resumes automatically:

  • pauseWhenOffscreen (default true) — pause while the target scrolls out of view.
  • pauseWhenHidden (default true) — pause while the document is hidden (e.g. a background tab).
  • respectReducedMotion (default false) — when enabled, hold a single static frame while the OS "prefers reduced motion" setting is on, and resume if the user turns it off. The u_reducedMotion uniform and state.reducedMotion are supplied regardless of this flag, so you can also handle reduced motion inside the shader. In renderMode: "demand" the motion gate does not apply (there is no loop to throttle).

WebGL2 / GLSL ES 3.00

Start your fragment shader with #version 300 es and the library automatically requests a WebGL2 context:

#version 300 es
precision highp float;

in vec2 v_uv;
out vec4 fragColor;

uniform float u_time;

void main() {
  fragColor = vec4(v_uv, abs(sin(u_time)), 1.0);
}

The internal vertex shader switches to a #version 300 es / in/out variant automatically. The v_uv varying name is the same as in WebGL1. You are responsible for writing precision, in vec2 v_uv;, and out vec4 — the library does not inject any preamble.

If WebGL2 is unavailable on the device, the instance transitions to status: "unsupported" and onError receives an UnsupportedError. No automatic downgrade is attempted.

Shaders without #version 300 es continue to use WebGL1 exactly as before.

Instance API

fx.ready;
fx.start();
fx.stop();
fx.render();
fx.resize();
fx.destroy();
await fx.setTexture("image", "/next-hero.webp");
await fx.setTextures({ image: "/next-hero.webp" });
fx.setUniform("progress", 0.5);
fx.setUniforms({ progress: 0.5, color: [1, 0, 0] });

Unsupported environments return a no-op instance. ready rejects and debug: true prints warnings.

GLSL Helpers

import { glsl, glslUtils } from "@frapx/shader/glsl";

const fragment = glsl`
precision highp float;
${glslUtils.coverUv}

uniform vec2 u_resolution;
uniform vec2 u_imageSize;
uniform sampler2D u_image;

void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;
  gl_FragColor = texture2D(u_image, coverUv(uv, u_resolution, u_imageSize));
}
`;

SSR Notes

The package is safe to import during SSR. Calling createShaderBackground() without a browser returns a no-op instance whose ready promise rejects.

Browser Support

Shaders without #version 300 es use WebGL1 / GLSL ES 1.00. Shaders that start with #version 300 es request WebGL2 / GLSL ES 3.00 automatically. WebGL2 support is additive and does not change the WebGL1 default path.