Utility for flickering text, images, and DOM elements with configurable timing, intensity, and advanced image manipulation. Includes a unified text writer (write/queue/add/remove), text presets (zalgo, terminal, neo, cosmic, horror), CSS filters, text effects (scramble, typewriter, decode), presets, plugins, timeline/keyframes, group orchestration, audio-reactive mode, canvas/WebGL renderers, and framework adapters.
📖 Full documentation — installation, text writer, framework adapters, presets, advanced usage, and migration from glitched-writer.
npm install @disclearing/flickerimport { createFlicker } from '@disclearing/flicker';
const el = document.querySelector('.my-text');
const controller = createFlicker(el, { interval: 100 });
controller.start();
// later: controller.stop(); controller.destroy();import { flickerElement, flickerSelector } from '@disclearing/flicker';
const ctrl = flickerElement('#logo');
if (ctrl) ctrl.start();
const controllers = flickerSelector('.flicker-me');
controllers.forEach((c) => c.start());engine:'timeout'(default) or'raf'for requestAnimationFrame-based timing.respectReducedMotion: Whentrue(default), honorsprefers-reduced-motionand disables or softens flicker.autoPauseOnHidden: Whentrue(default), pauses when the tab is hidden and resumes when visible.- Richer events:
onStart,onPause,onResume,onDestroy,onVisibilityChange; image sequences also supportonTransitionStart,onTransitionEnd,onLoop.
Always call destroy() when discarding a controller to clear timers and listeners.
Use built-in presets for common looks:
import { getFlickerPreset, getSequencePreset, getCombinedPreset } from '@disclearing/flicker';
// Flicker presets: neonSign, horrorGlitch, oldTV, warningAlarm
const opts = getFlickerPreset('horrorGlitch', { interval: 40 });
createFlicker(el, opts).start();
// Sequence presets: neon, horror, oldTV, warning
const seqOpts = getSequencePreset('horror', { images: ['/a.jpg', '/b.jpg'] });
createImageSequence(img, seqOpts).start();
// Combined
const combined = getCombinedPreset('horrorGlitch', 'horror', { sequence: { images: ['/1.jpg', '/2.jpg'] } });
createCombinedFlicker(img, combined).start();
// Text writer presets: zalgo, terminal, neo, cosmic, horror, encrypted, nier, default
const textOpts = getTextPreset('zalgo', { interval: 40 });
createTextWriter(el, textOpts).write('Hello world');Unified writer API: write(), writeAsync(), queue(), endless(), add(), remove() with scramble, typewriter, decode, or glyph-sub effects. Respects engine, respectReducedMotion, and autoPauseOnHidden like other controllers.
import { createTextWriter, getTextPreset } from '@disclearing/flicker';
const el = document.querySelector('.text-target');
const writer = createTextWriter(el, { mode: 'scramble', interval: 60, cursor: true });
writer.write('Hello'); // animate in one string
await writer.writeAsync('Done'); // Promise resolves when animation finishes
writer.queue(['Line 1', 'Line 2'], 500, true); // queue phrases, optional loop
writer.endless(['A', 'B', 'C'], 300); // same as queue(..., true)
writer.add(' more'); // append and animate new part only
writer.remove(3); // remove last 3 chars
writer.on('complete', () => console.log('done')); // multiple listeners
writer.off('complete', handler);
writer.pause();
writer.resume();
writer.destroy();- Custom event: When a write completes, the element dispatches
flicker-writer-finishedwithdetail: { text, length }so you can listen without callbacks. - Modes:
'scramble'(reveal from random glyphs),'typewriter'(character-by-character, optional human-like variance and punctuation pause),'decode'(each char resolves from random to final),'glyph-sub'(continuous substitution). - Options:
glyphPool,interval,minInterval/maxInterval,humanLike,pauseOnSpaces,punctuationPauseMs,cursor: true | '|' | { char, blink }(typing cursor; useblink: truefor blinking),seed(number for deterministic animations),html: 'strip' | 'preserve'(preserve tags when writing e.g.writer.write('<b>Hi</b>')),letterize: 'in-place' | 'fragment',onStep,onComplete, plus engine and a11y options. - Events:
writer.on('start' | 'step' | 'complete' | 'destroy' | 'visibilitychange', fn)andwriter.off(event, fn)for multiple listeners. - Helpers:
decodeEntities(),letterizeToFragment(),setLetterizedContent(),setLetterizedContentFromHtml()(parse HTML string and letterize only text nodes),createSeededRandom(seed)for deterministic RNG;runDecode()for one-shot decode effect.
Register custom transitions and effects:
import { registerTransition, registerEffect, createFlicker } from '@disclearing/flicker';
registerTransition('my-fade', async (element, newSrc, duration) => {
element.style.opacity = '0';
await new Promise((r) => setTimeout(r, duration / 2));
element.src = newSrc;
element.style.opacity = '1';
});
registerEffect('my-glow', (el, visible) => {
el.style.boxShadow = visible ? '0 0 20px rgba(255,0,0,0.5)' : 'none';
});
createFlicker(el, {}).start(); // registered effects run each tick
createImageSequence(img, { images: ['/a.jpg', '/b.jpg'], transition: 'my-fade' }).start();Timeline: run steps in order (flicker for N ms, pause, custom callback, etc.):
import { createTimeline } from '@disclearing/flicker';
const timeline = createTimeline([
{ type: 'flicker', element: titleEl, options: { interval: 50 }, duration: 300 },
{ type: 'pause', duration: 500 },
{ type: 'callback', fn: () => console.log('Done step 2') },
], { loop: false, onComplete: () => console.log('Timeline done') });
timeline.start();Group: start multiple controllers with a shared clock and optional phase offsets:
import { createGroup, createFlicker } from '@disclearing/flicker';
const c1 = createFlicker(el1, { interval: 100 });
const c2 = createFlicker(el2, { interval: 100 });
const group = createGroup([
{ controller: c1 },
{ controller: c2, phaseOffsetMs: 50 },
]);
group.start();Drive interval and intensity from microphone or audio element:
import { createAudioReactiveFlicker, isAudioReactiveSupported } from '@disclearing/flicker';
if (!isAudioReactiveSupported()) return;
const audioEl = document.querySelector('audio');
const ctrl = createAudioReactiveFlicker(domEl, {
source: audioEl,
onError: (err) => console.warn('Audio-reactive unavailable', err.message),
});
ctrl.start();Canvas, WebGL, and WebGPU renderers apply noise, scanline, or distortion to a source image/video. Canvas auto-resizes with the source, pauses when the tab is hidden (autoPauseOnHidden), and supports scale/throttleFps for performance. WebGL supports continuous rendering via createWebGLRenderer and exposes onContextLost/onError. WebGPU supports ready() promise, autoResize, and onDeviceLost. See docs/advanced.md.
import {
createCanvasRenderer,
createWebGLRenderer,
createWebGPURenderer,
isCanvasSupported,
isWebGLSupported,
isWebGPUSupported,
} from '@disclearing/flicker';
if (!isCanvasSupported()) return;
const renderer = createCanvasRenderer(videoEl, { type: 'scanline', scanlineSpacing: 4 });
renderer.start();
document.body.appendChild(renderer.canvas);
if (isWebGLSupported()) {
const webgl = createWebGLRenderer(videoEl, { type: 'distortion', autoResize: true });
webgl.start();
if (webgl.canvas) document.body.appendChild(webgl.canvas);
}
if (isWebGPUSupported()) {
const gpu = createWebGPURenderer(videoEl, { type: 'noise', noiseAmount: 0.12 });
await gpu.ready();
gpu.start();
if (gpu.canvas) document.body.appendChild(gpu.canvas);
}React (install react when using):
import { useFlicker, useFlickerController, useImageSequence, useTimeline, useTextWriter } from '@disclearing/flicker/react';
function MyComponent() {
const ref = useRef(null);
useFlicker(ref, { interval: 100 });
return <div ref={ref}>Flickering text</div>;
}
// Text writer: bind ref and get controller (write, queue, writeAsync, endless, etc.)
function WriterDemo() {
const ref = useRef(null);
const writerRef = useTextWriter(ref, { mode: 'typewriter', cursor: true });
useEffect(() => {
writerRef.current?.write('Hello world');
}, []);
return <div ref={ref} />;
}Vue 3 – text writer (composable or directive):
import { useTextWriter, textWriterDirective, unmountTextWriterDirective } from '@disclearing/flicker/vue';
// Composable: get write, queue, writeAsync, endless, etc.
const { controller, write, writeAsync } = useTextWriter(elementRef, { mode: 'scramble', cursor: { blink: true } });
onMounted(() => write('Hello'));
// Or directive: v-text-writer="options" then use ref to call writer methods
app.directive('text-writer', { mounted: textWriterDirective, unmounted: unmountTextWriterDirective });Svelte – text writer (action):
<script>
import { textWriter } from '@disclearing/flicker/svelte';
let writerOpts = { mode: 'typewriter', cursor: { char: '|', blink: true } };
</script>
<div use:textWriter={writerOpts} bind:this={el}></div>
<!-- Or get controller from action return: const result = textWriter(node, opts); result.controller.write('Hi'); -->Vue 3 (optional):
import { useFlicker, flickerDirective } from '@disclearing/flicker/vue';
// Composition: call start() in onMounted
const { start, stop } = useFlicker(elementRef, { interval: 100 });
// Directive
app.directive('flicker', { mounted: flickerDirective, unmounted: unmountFlickerDirective });Svelte (optional):
import { flicker, imageSequence } from '@disclearing/flicker/svelte';<div use:flicker={{ interval: 100 }}>Flickering</div>
<img use:imageSequence={{ images: ['/a.jpg', '/b.jpg'], interval: 500 }} alt="" />Expo / React Native (optional):
import { useExpoFlicker, useExpoImageSequence } from '@disclearing/flicker/expo';function GlitchImage() {
const { currentImage, controller: seq } = useExpoImageSequence({
images: ['https://example.com/1.png', 'https://example.com/2.png'],
interval: 500,
loop: true,
});
const { opacity, controller: flicker } = useExpoFlicker({ interval: 80, offOpacity: 0.15 });
useEffect(() => {
seq.start();
flicker.start();
return () => {
seq.destroy();
flicker.destroy();
};
}, [seq, flicker]);
return <Image source={{ uri: currentImage ?? undefined }} style={{ opacity, width: 200, height: 200 }} />;
}Validate options before use:
import {
validateFlickerOptions,
validateTextWriterOptions,
validateOrThrow,
} from '@disclearing/flicker';
const result = validateFlickerOptions({ interval: -1 });
if (!result.valid) console.error(result.errors);
const textResult = validateTextWriterOptions({ mode: 'decode', decodeDuration: -5 });
if (!textResult.valid) console.error(textResult.errors);
validateOrThrow(userOptions, validateFlickerOptions, 'Flicker');- Transitions:
instant,crossfade,slide-left/right/up/down,zoom,flicker, or custom viaregisterTransition. - Options:
interval,randomInterval,minInterval/maxInterval,transition,transitionDuration,loop,shuffle,startIndex,direction,preload,preloadAhead,duration, and callbacks includingonStart,onPause,onResume,onTransitionStart,onTransitionEnd,onLoop,onDestroy,onVisibilityChange,onError.
- Retry/backoff:
preloadImage(url, { retries: 2, backoffMs: 500 }). - Cache limits:
configurePreloader({ maxCacheSize: 100, evictionStrategy: 'leastRecentlyUsed' }). - Eviction:
evictStale(maxAgeMs). - Invalid numeric values are clamped to safe defaults (e.g. retries >= 0, concurrency >= 1, cache size >= 1).
- CSS filters:
applyFilters(el, { blur, contrast, hueRotate, saturate, chromaticAberration, rgbSplit }); usefiltersin flicker options for the "off" phase. - Text:
preparePerCharFlicker(container),runScrambleReveal(container, options),runGlyphSubstitution(container, options),runTypewriter(container, options),runDecode(container, options)(decode/decrypt: each char resolves from random glyphs). Options supportonStep(index, char, isComplete)andstartFromIndexfor writer add(). Container getsdata-flicker-state="writing"and classflicker-writingduring animation; spans getdata-flicker-char-index. - HTML/entities:
decodeEntities(str),letterizeToFragment(text),setLetterizedContent(container, text),setLetterizedContentFromHtml(container, htmlString). Usehtml: 'strip' | 'preserve'anddecodeEntitiesInin effect options.
Blinking cursor (use cursor: { char: '|', blink: true } and add CSS):
.flicker-cursor-blink {
animation: flicker-cursor-blink 0.8s step-end infinite;
}
@keyframes flicker-cursor-blink {
50% { opacity: 0; }
}HTML with preserved tags (e.g. <b>bold</b> stays bold):
createTextWriter(el, { html: 'preserve' }).write('<b>Hello</b> <i>world</i>');Deterministic animation (same seed = same pattern; useful for tests or replay):
createTextWriter(el, { mode: 'scramble', seed: 42 }).write('Reproducible glitch');- FlickerController:
start(),stop(),setOptions(),destroy(),isRunning. - ImageSequenceController: same plus
pause(),resume(),jumpTo(),next(),previous(),preloadAll(),isPaused,currentIndex,totalImages,currentImage. - CombinedFlickerController: both plus
state(visible, imageIndex, imageUrl).
MIT