halftone-webgl
v1.0.5
Published
GPU-accelerated halftone effect with WebGL — lightweight, Webflow-friendly
Maintainers
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-webglimport 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); // HTMLImageElementresize()
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% qualitydestroy()
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
