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

halftone-webgl

v1.0.5

Published

GPU-accelerated halftone effect with WebGL — lightweight, Webflow-friendly

Readme

halftone-webgl

GPU-accelerated halftone effects for the web. Lightweight, interactive, Webflow-friendly.

Features

  • GPU-powered — Fragment-shader halftone math, runs entirely on the GPU
  • 6 dot shapes — circle, square, diamond, line, cross, ellipse
  • 4 color modes — mono, auto (source colors), CMYK separation, duotone
  • 10 mouse interactions — reveal, magnify, warp, ripple, vortex, colorShift, focus, comet, sparkle
  • Any source — static images, video, webcam, or no source at all
  • Content overlay — Children are auto-promoted above the canvas; buttons/links pause interaction on hover
  • Zero-config — Works with just data attributes, no JavaScript required
  • ~21 KB gzipped (includes OGL WebGL abstraction)

Quick Start (CDN)

Add a script tag and use data attributes — no build step needed.

<script src="https://unpkg.com/halftone-webgl/dist/halftone-webgl.min.js"></script>

<div data-hwgl-element
     data-hwgl-source="photo.jpg"
     data-hwgl-shape="circle"
     data-hwgl-color-mode="mono"
     data-hwgl-interaction="sparkle"
     style="width: 100%; height: 400px;">
  <h1>Your content here</h1>
</div>

The library auto-initializes on DOMContentLoaded for every element with data-hwgl-element.

Video Source

<div data-hwgl-element
     data-hwgl-source="video.mp4"
     data-hwgl-interaction="sparkle"
     style="width: 100%; height: 400px;">
</div>

Webcam Source

<div data-hwgl-element
     data-hwgl-source="webcam"
     data-hwgl-interaction="warp"
     style="width: 100%; height: 400px;">
</div>

Quick Start (npm)

npm install halftone-webgl
import HalftoneWebGL from 'halftone-webgl';

const ht = new HalftoneWebGL({
  container: '#my-element',
  source: 'photo.jpg',
  shape: 'circle',
  colorMode: 'mono',
  interaction: 'sparkle',
});

// Update options at runtime
ht.set('frequency', 60);
ht.set({ shape: 'diamond', colorMode: 'cmyk' });

// Load a new source
await ht.loadSource('video.mp4');

// Clean up
ht.destroy();

Data Attributes API

Every configuration option can be set via a data-hwgl-* attribute on the container element.

| Attribute | Config Key | Type | Default | |---|---|---|---| | data-hwgl-element | (marker) | — | — | | data-hwgl-source | source | string | null | | data-hwgl-frequency | frequency | number | 40 | | data-hwgl-angle | angle | number | 0 | | data-hwgl-shape | shape | string | 'circle' | | data-hwgl-scale | scale | number | 1.0 | | data-hwgl-softness | softness | number | 0.5 | | data-hwgl-gap | gap | number | 0 | | data-hwgl-contrast | contrast | number | 1.8 | | data-hwgl-brightness | brightness | number | 1.0 | | data-hwgl-invert | invert | boolean | false | | data-hwgl-min-dot | minDot | number | 0 | | data-hwgl-color-mode | colorMode | string | 'mono' | | data-hwgl-color | color | hex string | '#E85002' | | data-hwgl-color-b | colorB | hex string | '#000000' | | data-hwgl-bg-color | bgColor | hex string | '#050510' | | data-hwgl-cmyk-angles | cmykAngles | comma-separated | 15,75,0,45 | | data-hwgl-fit | fit | string | 'cover' | | data-hwgl-interaction | interaction | string | 'none' | | data-hwgl-radius | radius | number | 0.3 | | data-hwgl-strength | strength | number | 0.5 | | data-hwgl-trail-fade | trailFade | number | 0.03 | | data-hwgl-dpr | dpr | 'auto' or number | 'auto' | | data-hwgl-z-index | zIndex | number | 0 | | data-hwgl-pause-selector | pauseSelector | CSS selector | 'a, button, [data-hwgl-pause]' |

JavaScript API

Constructor

const ht = new HalftoneWebGL({
  container: '#my-element', // CSS selector or DOM element (required)
  source: 'photo.jpg',     // any config option can be passed here
  // ...
});

Methods

set(key, value) / set({ ... })

Update one or more configuration options at runtime.

ht.set('frequency', 60);
ht.set({ shape: 'diamond', colorMode: 'cmyk', contrast: 2.5 });

loadSource(src)

Load a new image, video, or webcam source. Returns a Promise.

await ht.loadSource('photo.jpg');       // image URL
await ht.loadSource('video.mp4');       // video URL
await ht.loadSource('webcam');          // webcam
await ht.loadSource(myVideoElement);    // HTMLVideoElement
await ht.loadSource(myImageElement);    // HTMLImageElement

resize()

Manually trigger a resize. Automatically called via ResizeObserver — you rarely need this.

pause() / resume()

Stop or start the animation loop.

ht.pause();
ht.resume();

snapshot(type?, quality?)

Capture the current frame as a data URL.

const dataUrl = ht.snapshot();                        // PNG
const dataUrl = ht.snapshot('image/jpeg', 0.92);      // JPEG at 92% quality

destroy()

Clean up all resources — stops animation, removes the canvas, releases video/webcam streams, clears event listeners.

ht.destroy();

Properties

| Property | Type | Description | |---|---|---| | canvas | HTMLCanvasElement | The WebGL canvas element | | config | object | Copy of the current configuration | | isPlaying | boolean | Whether the animation loop is running |

Events

ht.on('ready', () => { /* first frame rendered */ });
ht.on('sourceload', () => { /* source texture loaded */ });
ht.on('resize', ({ w, h }) => { /* container resized */ });
ht.on('error', (err) => { /* something went wrong */ });

| Event | Data | Description | |---|---|---| | ready | — | First frame rendered or source loaded | | sourceload | — | Source texture loaded successfully | | resize | { w, h } | Container was resized | | error | Error | An error occurred |

Configuration Reference

Screen

| Option | Type | Range | Default | Description | |---|---|---|---|---| | frequency | number | 5–150 | 40 | Dot density (dots per row) | | angle | number | degrees | 0 | Screen rotation angle |

Dot

| Option | Type | Values / Range | Default | Description | |---|---|---|---|---| | shape | string | circle square diamond line cross ellipse | 'circle' | Dot shape | | scale | number | 0.1–2 | 1.0 | Dot size multiplier | | softness | number | 0–1 | 0.5 | Edge softness | | gap | number | 0–1 | 0 | Gap between dots |

Tone

| Option | Type | Range | Default | Description | |---|---|---|---|---| | contrast | number | 0.2–5 | 1.8 | Tonal contrast | | brightness | number | 0.1–3 | 1.0 | Brightness multiplier | | invert | boolean | — | false | Invert tones | | minDot | number | 0–1 | 0 | Minimum dot size |

Color

| Option | Type | Values / Range | Default | Description | |---|---|---|---|---| | colorMode | string | mono auto cmyk duotone | 'mono' | Color rendering mode | | color | hex string | — | '#E85002' | Primary dot color (mono highlight, duotone highlight) | | colorB | hex string | — | '#000000' | Secondary color (duotone shadow) | | bgColor | hex string | — | '#050510' | Background color | | cmykAngles | number[4] | degrees | [15, 75, 0, 45] | Per-channel screen angles [C, M, Y, K] |

Source

| Option | Type | Values | Default | Description | |---|---|---|---|---| | source | string / null | URL, 'webcam', null | null | Image/video URL or webcam | | fit | string | cover contain fill | 'cover' | How the source fits the container |

Interaction

| Option | Type | Values / Range | Default | Description | |---|---|---|---|---| | interaction | string | none reveal magnify warp ripple vortex colorShift focus comet sparkle | 'none' | Mouse interaction effect | | radius | number | 0–1 | 0.3 | Interaction area radius | | strength | number | 0–1 | 0.5 | Interaction intensity | | trailFade | number | 0–1 | 0.03 | Trail fade rate (comet/sparkle) |

Container

| Option | Type | Values | Default | Description | |---|---|---|---|---| | dpr | 'auto' / number | — | 'auto' | Device pixel ratio (auto caps at 2) | | zIndex | number | — | 0 | Canvas z-index | | pauseSelector | string | CSS selector | 'a, button, [data-hwgl-pause]' | Elements that pause interaction on hover |

Content Overlay Pattern

Any children inside the halftone container are automatically promoted above the canvas:

<div data-hwgl-element data-hwgl-source="bg.jpg" data-hwgl-interaction="sparkle"
     style="height: 400px;">
  <!-- These sit above the halftone canvas automatically -->
  <h1>Heading</h1>
  <p>Paragraph text — fully selectable.</p>
  <button>Clickable button</button>
</div>

How it works:

  • The canvas is injected with position: absolute; pointer-events: none
  • Child elements get position: relative; z-index: 1
  • Buttons, links, and [data-hwgl-pause] elements pause the interaction effect on hover so they're easy to click

Custom elements can opt into pause behavior:

<div data-hwgl-pause>This pauses interaction on hover</div>

Browser Support

Requires WebGL 1 — supported in all modern browsers (Chrome, Firefox, Safari, Edge).

License

MIT