@isonimus/glitch-js
v2.0.0
Published
A lightweight, dependency-free, vanilla JavaScript library for stackable DOM glitch effects.
Maintainers
Readme
Glitch.js
A lightweight, dependency-free TypeScript library for applying stackable digital distortion and glitch effects to standard HTML/DOM elements. Built with GPU-accelerated SVG matrix filters, performance-optimized render loops, strict type safety, and interactive triggers.
🔗 Live Playground / Interactive Demo
Features
- Stackable & Composeable: Combine RGB split, slice shifting, text scramble, decrypt reveals, container shaking, opacity flickers, CRT scanlines, and cinematic hologram projection on a single DOM element.
- Performance First: Leverages GPU-accelerated SVG color matrix filters and CSS transforms running inside a
requestAnimationFramerender loop. - Event-Driven Sync: Uses
MutationObserverto automatically sync text changes and DOM updates from your main application into the glitch overlays without polling overhead. - Scroll-Visibility Triggers: Native
IntersectionObserversupport allows triggering glitch effects only when elements scroll into view. - Mouse Velocity Interaction: Dynamically scales glitch intensity, offsets, and frequency based on the speed of the user's cursor over the element.
- NPM Ready & Strongly Typed: Fully rewritten in TypeScript, shipping with rolled-up
.d.tsdeclaration files for complete IDE autocomplete and static type safety. - Zero Dependencies: Pure, vanilla library. No external styling or framework integrations required.
Installation
Package Manager
Install via npm:
npm install @isonimus/glitch-jsThen import it into your build files:
import { Glitch, Effects } from '@isonimus/glitch-js';CDN / Direct Browser Import
For static sites, quick prototyping, or simple HTML projects, you can load the library directly from a CDN without any bundle configuration:
Option A: ES Modules (Recommended)
Import the library inside a <script type="module"> block using jsDelivr or esm.sh:
<script type="module">
import { Glitch, Effects } from 'https://cdn.jsdelivr.net/npm/@isonimus/[email protected]/dist/lib/glitch.es.js';
const element = document.querySelector('.glitch-title');
new Glitch(element, {
effects: [Effects.rgbSplit()]
});
</script>Option B: Classical Script Tag (UMD Global)
Include the script tag, which exposes the library globally under the window.Glitch namespace:
<!-- Load UMD bundle -->
<script src="https://cdn.jsdelivr.net/npm/@isonimus/[email protected]/dist/lib/glitch.umd.js"></script>
<script>
// Destructure from the global Glitch object
const { Glitch, Effects } = window.Glitch;
const element = document.querySelector('.glitch-title');
new Glitch(element, {
effects: [Effects.flicker()]
});
</script>Quick Start
1. Simple Scramble & Flicker on Hover (TypeScript / JavaScript)
Apply a hover-triggered text scramble and brightness flicker to any element (e.g., a button or heading):
import { Glitch, Effects } from '@isonimus/glitch-js';
const heading = document.querySelector('.glitch-heading') as HTMLElement;
const glitch = new Glitch(heading, {
trigger: 'hover',
effects: [
Effects.scramble({ frequency: 0.3, scrambleChance: 0.3 }),
Effects.flicker({ frequency: 0.2, minOpacity: 0.4 })
]
});2. High-Intensity Cyberpunk Effect (Always Active)
Compose a combined RGB Split, CRT Scanline grid, and Slice Shift overlay:
import { Glitch, Effects } from '@isonimus/glitch-js';
const terminal = document.querySelector('.terminal-view') as HTMLElement;
const controller = new Glitch(terminal, {
trigger: 'always',
effects: [
// GPU-accelerated RGB split
Effects.rgbSplit({ maxOffset: 10, frequency: 0.4, blendMode: 'screen' }),
// CRT scanline overlay with subtle pulse animation
Effects.scanlines({ opacity: 0.15, pulse: true }),
// Slice shifting bands
Effects.slice({ maxOffset: 12, frequency: 0.3 })
]
});3. Mouse Velocity Coupled Interactivity
Make the glitch effect intensify dynamically as the user moves their mouse cursor faster over the element:
import { Glitch, Effects } from '@isonimus/glitch-js';
const card = document.querySelector('.hero-card') as HTMLElement;
const mouseGlitch = new Glitch(card, {
trigger: 'hover',
effects: [
Effects.rgbSplit({
maxOffset: 6,
frequency: 0.2,
mouseInteract: true, // Turn on velocity scaling
mouseSensitivity: 2.0 // Higher factor = stronger velocity reaction
}),
Effects.shake({
amplitudeX: 4,
amplitudeY: 3,
frequency: 0.3,
mouseInteract: true,
mouseSensitivity: 1.5
})
]
});4. Cinematic Hologram Projection
Turn any element (portrait, card, heading) into a semitransparent sci-fi hologram with a sweeping interference band and gentle hover:
import { Glitch, Effects } from '@isonimus/glitch-js';
const portrait = document.querySelector('.avatar') as HTMLElement;
const hologram = new Glitch(portrait, {
trigger: 'always',
effects: [
Effects.hologram({
color: '#00d9ff', // Projection tint & glow
opacity: 0.82, // Base transparency
glowIntensity: 0.6, // Strength of tint and outer glow
scanSpeed: 1.2, // Interference band sweep speed
flickerFrequency: 0.1, // Chance of a glitch flash per frame
floatAmplitude: 4 // Vertical hover in pixels
}),
// Optional: layer scanlines for extra CRT texture
Effects.scanlines({ opacity: 0.12, pulse: true })
]
});5. Decrypt Reveal on Scroll
Reveal a heading like a decryption in progress: a same-length block of rolling characters that locks into the real text one character at a time.
import { Glitch, Effects } from '@isonimus/glitch-js';
const headline = document.querySelector('.classified-headline') as HTMLElement;
const reveal = new Glitch(headline, {
trigger: 'scroll', // Replays each time the element re-enters the viewport
effects: [
Effects.decrypt({
duration: 1800, // Time from fully masked to fully readable
rollInterval: 40, // How fast the unlocked characters re-roll
revealOrder: 'forward', // 'random' locks positions in a scattered order
maskWhitespace: true, // Hide word boundaries so it reads as ciphertext
preformatted: false, // true only if the target preserves whitespace
onComplete: () => headline.classList.add('is-decrypted')
})
]
});API Reference
new Glitch(element, options)
Instantiates the distortion engine on a target DOM element.
Options (GlitchOptions)
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| effects | GlitchEffect[] | [] | List of effect modules to apply (e.g. Effects.rgbSplit()). |
| trigger | GlitchTrigger | 'always' | Activation mode: 'always', 'hover', 'click', 'scroll', or 'manual'. |
| active | boolean | true | Set to false to prevent the engine from starting automatically. |
Methods
start(): Manually starts the glitch render loop.stop(): Stops the loop and resets all styles and text contents to their original state.updateOptions(newOptions): Cleanly stops, reconfigures, and restarts the engine with new options on the fly.destroy(): Shuts down the engine, removes all observers/event listeners, deletes all clone overlays from the DOM, and restores original styles.
Effect Modules
Effects.rgbSplit(options?: RGBSplitOptions)
Clones the element to separate it into Red and Cyan color channels, offsetting them horizontally.
maxOffset(number, default:8): Maximum horizontal/vertical offset distance in pixels.frequency(number, default:0.3): Chance (from0to1) of triggering a split on each frame.blendMode(string, default:'screen'): CSS mix-blend-mode (e.g.,'screen','multiply').mouseInteract(boolean, default:false): Enable to scale offset and frequency with mouse velocity.mouseSensitivity(number, default:1.5Multiplier for mouse velocity scaling.
Effects.slice(options?: SliceOptions)
Cuts horizontal slices out of overlay clones and shifts them left or right.
maxOffset(number, default:15): Maximum slice translation distance in pixels.frequency(number, default:0.25): Chance of triggering a slice displacement on each frame.mouseInteract(boolean, default:false): Scales displacement and trigger frequency with cursor speed.mouseSensitivity(number, default:1.5): Sensitivity factor for velocity coupling.
Effects.scramble(options?: ScrambleOptions)
Scrambles the text nodes inside the element using a character pool, while preserving the parent HTML structure.
characters(string, default:'01010101XYZ$#@%&*[]<>?/\\+=-_'): The pool of glyphs to draw from.frequency(number, default:0.2): Chance of scrambling text on each frame.scrambleChance(number, default:0.25): Chance of any individual character in a text node being scrambled.
Effects.decrypt(options?: DecryptOptions)
Replaces the text with a same-length string of rolling characters, then locks them into place one position at a time until the real text is revealed — the "hacker movie decryption" reveal.
characters(string, default:'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'): The pool of glyphs the rolling characters are drawn from.duration(number, default:2000): Milliseconds for the full reveal, from fully masked to fully readable.rollInterval(number, default:50): Milliseconds between re-rolls of the still-masked characters. Rolling on every frame reads as blur rather than as rolling, so this is deliberately slower than the frame rate.revealOrder('forward' | 'random', default:'forward'): Whether positions lock left to right, or in a random order.maskWhitespace(boolean, default:true): Whether rendered spaces are masked too.truehides word boundaries, so the masked text reads as ciphertext;falsekeeps the word shapes visible. Only whitespace the browser actually paints is ever masked: a single space between two non-whitespace characters, and non-breaking spaces. Whitespace runs, leading and trailing whitespace, line breaks and tabs are left alone, because layout collapses them — masking one of those would paint a character where the browser paints none, lengthening the line and adding line boxes for the length of the reveal. Source indentation in hand-formatted markup is the usual case, and in a flex or grid row whitespace-only text nodes are dropped entirely, so masking them would insert items.preformatted(boolean, default:false): Declares that the target renders whitespace verbatim —white-space: pre,pre-wrap,break-spaces, or a<pre>element. ThenmaskWhitespacemasks whitespace runs and indentation too, since layout paints all of it. It is an explicit option rather than something the library detects, because a detected version could not be tested:getComputedStylereports no usablewhite-spaceunder jsdom. Tabs, line breaks, and whitespace-only text nodes stay out of the mask even when this istrue— a tab advances to the next tab stop and a line break ends the line, so neither is one character wide.onComplete(() => void, optional): Called once each time a reveal finishes.
Unlike the other modules this effect is finite: it runs to completion and then stops doing work. Its reset hook — which stop() calls — rearms it, so hover and click triggers replay the reveal, and manual triggers can replay it with stop() followed by start().
Note: in a
white-space: precontainer (a<pre>block or ASCII art) every space is painted, so the rule above leaves the runs used for column alignment visible. Passpreformatted: trueto mask those as well.
Note: because every character is replaced by another of the same count rather than the same width, a proportional font will reflow slightly on each roll. Use a monospace font (or
font-variant-numeric: tabular-numsfor digits) for a reveal that holds its layout still.
Stacking with
scramble: both modules rewrite the same text nodes, and each one reads the pristine text rather than what is currently displayed, so whichever appears later in theeffectsarray is the one you see. Listingdecryptafterscrambletherefore gives a clean reveal followed by ambient scrambling once the reveal completes, which is usually what you want.
Effects.shake(options?: ShakeOptions)
Translates the base container rapidly to simulate vibration.
amplitudeX(number, default:6): Maximum horizontal vibration distance in pixels.amplitudeY(number, default:4): Maximum vertical vibration distance in pixels.frequency(number, default:0.4): Chance of vibration occurring on each frame.mouseInteract(boolean, default:false): Scales amplitude and frequency with mouse velocity.mouseSensitivity(number, default:1.5): Sensitivity factor for velocity coupling.
Effects.flicker(options?: FlickerOptions)
Randomly toggles the brightness and opacity of the element.
minOpacity(number, default:0.2): Minimum opacity limit during a flicker event.frequency(number, default:0.15): Chance of triggering a flicker.
Effects.scanlines(options?: ScanlineOptions)
Overlays custom grid lines resembling retro CRT terminal monitors.
opacity(number, default:0.12): Base opacity of the scanline overlay.pulse(boolean, default:true): Enable to animate the scanlines with a heartbeat/sine opacity cycle.
Effects.hologram(options?: HologramOptions)
Renders the element as a cinematic sci-fi projection: a semitransparent, color-tinted layer with a sweeping interference band, faint holographic banding, gentle vertical hovering, and intermittent glitch flashes.
color(string, default:'#00d9ff'): Projection tint and glow color (any CSS color).opacity(number, default:0.85): Base transparency of the projected element.glowIntensity(number, default:0.5): Strength of the color tint and outer glow.scanSpeed(number, default:1): Speed of the vertically sweeping interference band.flickerFrequency(number, default:0.08): Chance per frame of a glitch/interference flash.floatAmplitude(number, default:3): Pixel amplitude of the gentle vertical hover.
Local Development & Contributing
We use Vite for local serving and bundling, and Vitest for DOM unit testing.
Setup
- Clone this repository.
- Install dependencies:
npm install
Scripts
- Start Playground Dev Server:
Loads the interactive cyberpunk demo playground atnpm run devhttp://localhost:5173. - Run Unit Tests:
npm run test - Run Code Coverage:
npm run test:coverage - Build Static Playground Site:
Outputs deploy-ready web pages intonpm run build/dist(used by GitHub Pages). - Build Library Module:
Outputs ESM, UMD, and DTS declaration files intonpm run build:lib/dist/lib(used by npm publishing).
