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

@winterrabe/glintjs

v0.2.2

Published

Lightweight WebGL shader backgrounds for modern websites

Readme

img

GlintJS

Attach interactive GLSL fragment shaders to any HTML element with a few lines of code.

Live Demo

Source Code

Features

  • Mouse tracking — shader receives live cursor position
  • Click pulse — animated ripple effect on click with easing control
  • Custom uniforms — pass any value from JavaScript directly into your shader
  • Element backgrounds — attach to any element, not just full-page canvases
  • Auto-pause — rendering stops when the canvas scrolls out of view
  • Scale modifier — adjust the shader coordinate space independently of canvas size
  • Responsive — works on any screen size

Why GlintJS?

GlintJS focuses on one thing:

Attach interactive GLSL fragment shaders to any HTML element with minimal setup:

  • No scene graph
  • No cameras
  • No meshes
  • No Three.js

Examples

Example images

Installation

npm install @winterrabe/glintjs

Or via CDN (no build step required):

<script src="https://cdn.jsdelivr.net/npm/@winterrabe/glintjs/dist/glint-canvas.global.js"></script>

Quick Start

import { GlintCanvas } from "glintjs";
import myShader from "./my-shader.glsl";

const canvas = new GlintCanvas({
  element: document.querySelector("#glcanvas"),
  fragmentSource: myShader,// string
});

canvas.startRender();

As an element background

const canvas = new GlintCanvas({
  element: document.querySelector("#glcanvas"),
  targetElement: document.querySelector("#my-card"),
  fragmentSource: myShader,// string
});

canvas.startRender();

When targetElement is provided, GlintJS automatically positions the canvas behind the element's content and attaches mouse event listeners to it.

Built-in Uniforms

These uniforms are available in every shader automatically:

| Uniform | Type | Description | |---|---|---| | uTime | float | Elapsed time in seconds | | uResolution | vec3 | Canvas dimensions (x, y, 1.0) | | uMouse | vec2 | Mouse position in canvas pixels | | uPulse | float | Pulse animation value (0.0 – 1.0) | | uPulsePos | vec2 | Canvas position of the last click | | uScale | vec2 | Scale modifier (default: 1.0, 1.0) |

Minimal fragment shader

precision highp float;

uniform float uTime;
uniform float uPulse;
uniform vec2 uMouse;
uniform vec3 uResolution;
uniform vec2 uPulsePos;
uniform vec2 uScale;

void main() {
   
    vec2 fragCoord = gl_FragCoord.xy / uScale.xy;
    vec2 uv = (fragCoord.xy / uResolution.xy -.5) * 2.;
    float aspect = uResolution.x / uResolution.y;
    uv.x *= aspect;

    vec2 mouseTarget = uMouse.xy / uScale.xy;
    vec2 mouse = (mouseTarget / uResolution.xy - .5) * 2.;
    mouse.x *= aspect;

    gl_FragColor = vec4(uv.xy, 0.0, 1.0);
}

Options

interface ICanvasOptions {
  /** Canvas element to render on */
  element: HTMLCanvasElement;
  /** Element to use as background target */
  targetElement?: HTMLElement;
  /**Default is 60. */
  fps?: number
  /**Default is 30. */
  mobileFps?: number
  /** Max device pixel ratio. Default: 2 */
  maxPixelRatio?: number
  /** GLSL fragment shader source */
  fragmentSource?: string;
  /** Speed of the click pulse animation (default: 1) */
  pulseSpeed?: number;
  /** Additional custom uniforms */
  uniforms?: Uniform[];
  /** Scale the shader coordinate space (default: Vector2(1, 1)) */
  scaleModifier?: Vector2;
  /** Override the pulse easing function */
  pulseEasingOverride?: (x: number) => number;
  /** Called every frame before rendering */
  onUpdate?: (canvas: GlintCanvas) => void;

  onHover?: (canvas: GlintCanvas) => void;
  onPause?: (canvas: GlintCanvas) => void;
  onUnpause?: (canvas: GlintCanvas) => void;
}

Custom Uniforms

Pass additional values from TypeScript into your shader:

const canvas = new GlintCanvas({
  element: document.querySelector("#glcanvas"),
  fragmentSource: myShader,
  uniforms: [
    { name: "uBrightness", value: 0.8 },
    { name: "uOffset", value: new Vector2(0.5, 0.25) },
  ],
});

Update them at runtime:

canvas.additionalUniforms.find(u => u.name === "uBrightness").value = 1.0;

Or use onUpdate for per-frame changes:

const canvas = new GlintCanvas({
  element: document.querySelector("#glcanvas"),
  fragmentSource: myShader,
  uniforms: [{ name: "uBrightness", value: 0.0 }],
  onUpdate: (c, time, delta)  => {
    c.additionalUniforms[0].value = Math.sin(time / 1000);
  },
});

API

startRender()

Starts the render loop.

canvas.startRender();

pause() / unpause()

Manually pause or resume rendering.

destroy()

Removes all event listeners and cleans up WebGL resources.

setCustomUniform(uniform: Uniform)

Update a single uniform manually outside of onUpdate.

Plain JS (no bundler)

<canvas id="glcanvas"></canvas>
<div id="target">Hover me</div>

<script src="https://cdn.jsdelivr.net/npm/@winterrabe/glintjs/dist/glint-canvas.global.js"></script>
<script>
  const { GlintCanvas, Vector2 } = GlintCanvas;

  const shader = `
        precision highp float;

        uniform float uTime;
        uniform float uPulse;
        uniform vec2 uMouse;
        uniform vec3 uResolution;
        uniform vec2 uPulsePos;
        uniform vec2 uScale;

        void main() {
        
            vec2 fragCoord = gl_FragCoord.xy / uScale.xy;
            vec2 uv = (fragCoord.xy / uResolution.xy -.5) * 2.;
            float aspect = uResolution.x / uResolution.y;
            uv.x *= aspect;
            
            vec2 mouseTarget = uMouse.xy / uScale.xy;
            vec2 mouse = (mouseTarget / uResolution.xy - .5) * 2.;
            mouse.x *= aspect;

            gl_FragColor = vec4(uv.xy, 0.0, 1.0);
        }
  `;

  const canvas = new GlintCanvas({
    element: document.querySelector("#glcanvas"),
    targetElement: document.querySelector("#target"),
    fragmentSource: shader,
  });

  canvas.startRender();
</script>

Limitations

Browser WebGL context limits

GlintJS creates a WebGL context for each active GlintCanvas instance. Modern browsers limit the number of active WebGL contexts per page or browser process to protect GPU memory and overall system stability.

If too many WebGL contexts are active at the same time, the browser may automatically lose older contexts. This can result in warnings such as:

Too many active WebGL contexts. Oldest context will be lost.

The exact limit depends on the browser, device, operating system, graphics driver, and available GPU resources. Mobile browsers and low-power devices may reach this limit earlier than desktop browsers.

GlintJS includes lazy initialization and cleanup mechanisms to reduce the number of active WebGL contexts. Still, for best results:

  • Avoid running a large number of GlintCanvas instances at the same time.
  • Prefer decorative shader effects only where they add visible value.
  • Use lower FPS values for background or subtle effects.
  • Use reduced settings on mobile devices.
  • Destroy unused instances when they are no longer needed.
  • Consider using one shared or persistent background effect instead of many independent effects.

For pages with many shader-enhanced elements, it is recommended to initialize effects only when they are near the viewport and release inactive contexts when they are no longer visible.