Skip to content
IsonimusPublic

About

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.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Glitch.js

CI Status NPM Version License: MIT

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 requestAnimationFrame render loop.
  • Event-Driven Sync: Uses MutationObserver to automatically sync text changes and DOM updates from your main application into the glitch overlays without polling overhead.
  • Scroll-Visibility Triggers: Native IntersectionObserver support 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.ts declaration 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-js

Then 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/glitch-js@1.0.2/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/glitch-js@1.0.2/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.
respectReducedMotion boolean true Honour the reader's prefers-reduced-motion: reduce setting. See Reduced Motion.

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.

GlitchEffect

The shape every effect module has, and what a custom effect implements.

Member Type Description
name string Identifies the effect.
clones number? How many overlay clones the effect paints on. The engine owns the pool: it sizes it to the largest count any active effect declares, builds it before the first setup, and removes it once no active effect asks for one.
setup (instance) => void Runs once when the engine initialises, and again after updateOptions.
update (instance, time) => void Runs on every animation frame while the engine is running.
reset (instance) => void Undoes what update painted; runs on stop() and when the motion preference changes.
cleanup (instance) => void Runs when updateOptions drops the effect, and on destroy(). Clears what the effect painted on shared nodes such as clones, and removes any DOM of its own.

Effects that declare clones share one pool, so two effects asking for two clones get the same two, not four. An effect clears its own paint in cleanup rather than removing the clones, because the pool may outlive it to serve another effect.


Reduced Motion

Readers who ask their operating system for less motion get a still version of every effect, with no configuration. The library watches (prefers-reduced-motion: reduce) and follows it live, so changing the setting while the page is open settles the effects immediately rather than waiting for a reload.

Holding still is not the same as switching off: what makes each effect recognisable stays, and only the movement stops.

Effect While motion is reduced
rgbSplit Clones stay split by half the configured offset, with the channel filters intact. No jitter.
slice No slicing.
scramble Off — the text stays readable.
shake No displacement.
flicker Off entirely.
scanlines Overlay stays at its resting opacity; no pulse.
hologram Tint, banding and glow stay; no float, no sweep, no dropout flashes.
decrypt Lands on the plaintext at once. onComplete still fires exactly once.

flicker is the one that stops rather than slows: a flashing element is a photosensitivity risk, not merely a preference (WCAG 2.3.1).

Where the motion is the content — a demo of the effect itself, say — opt out explicitly:

const glitcher = new Glitch(target, {
  respectReducedMotion: false,
  effects: [Effects.rgbSplit()]
});

That overrides an accessibility preference the reader deliberately set, so it wants a reason. A custom effect reads instance.motionReduced to decide its own still path.


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 (from 0 to 1) 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.5 Multiplier 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. true hides word boundaries, so the masked text reads as ciphertext; false keeps 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. Then maskWhitespace masks 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: getComputedStyle reports no usable white-space under the headless DOM the test suite runs on (happy-dom). Tabs, line breaks, and whitespace-only text nodes stay out of the mask even when this is true — 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: pre container (a <pre> block or ASCII art) every space is painted, so the rule above leaves the runs used for column alignment visible. Pass preformatted: true to 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-nums for 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 the effects array is the one you see. Listing decrypt after scramble therefore 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

  1. Clone this repository.
  2. Install dependencies:
    npm install

Scripts

  • Start Playground Dev Server:
    npm run dev
    Loads the interactive cyberpunk demo playground at http://localhost:5173.
  • Run Unit Tests:
    npm run test
  • Run Code Coverage:
    npm run test:coverage
  • Build Static Playground Site:
    npm run build
    Outputs deploy-ready web pages into /dist (used by GitHub Pages).
  • Build Library Module:
    npm run build:lib
    Outputs ESM, UMD, and DTS declaration files into /dist/lib (used by npm publishing).

About

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.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages