
GifScratcher is a vanilla JavaScript library for scrubbing animated GIFs by moving the pointer across an image.
It supports automatic playback and optional transport controls, and includes a standalone player for horizontal sprite strips.
Features
- The original GIF
<img>remains visible when JavaScript is disabled. - Moving or dragging the pointer across the canvas selects GIF frames in either direction.
- Optional play/pause buttons, frame stepping, and a seek slider.
- A configurable scrub cursor and loading indicator.
- Playback speed and interaction settings can change after initialization.
- GIF decoding through
ImageDecoderwhere available, with a bundledgifuct-jsfallback.
How To Use It
Installation
Include dist/gifscratcher.js and dist/gifscratcher.css in your web page.
<link rel="stylesheet" href="/assets/gifscratcher/gifscratcher.css">
<script type="module">
import { GifScratcher } from '/assets/gifscratcher/gifscratcher.js';
new GifScratcher('#gif-demo');
</script>
Basic Usage
Place an animated .gif inside the target element and initialize GifScratcher after the markup.
<div id="gif-demo"> <img src="/images/rotation.gif" alt="A product rotating in place"> </div>
All Configuration Options
Initialize the GIF player with new GifScratcher(target, options?). The target argument accepts a CSS selector or an HTMLElement. The optional options object accepts these settings:
gif(string, default''): URL of a real animated GIF. An explicit URL takes precedence over the first<img>inside the target. The source is loaded during initialization.auto(boolean, defaulttrue): Starts automatic playback and resumes it after the pointer leaves the canvas.scrub(boolean, defaulttrue): Enables pointer-based frame selection on hover or drag.cursor(boolean, defaulttrue): Adds the custom scrub cursor.loader(boolean, defaulttrue): Displays a spinner during GIF loading and decoding.player(boolean, defaultfalse): Adds play/pause, previous/next-frame buttons, a seek slider, and a frame counter.speed(number, default1): Playback speed multiplier.
Playback Methods And State
play(): Starts or resumes automatic playback.stop(): Stops playback at the current frame.render(index: number): Displays a zero-based frame index, wrapping values outside the frame count.rewind(): Displays the previous frame.forward(): Displays the next frame.setOptions(options: Partial<GifScratcherOptions>): Changesauto,scrub,cursor,player, andspeedon an existing instance.gifandloaderonly affect initial loading.destroy(): Detaches event listeners, removes the canvas and generated controls, and releases decoded frames. It does not restore the original<img>.
Two read-only getters expose playback state:
frame(number): Current zero-based frame index.isPlaying(boolean):trueduring automatic playback.
Events
Register listeners before creating the instance. Two events are dispatched on the target element:
gifscratcher:ready: Loading and decoding have finished.event.detailcontainsframes(number) anddecoder(string).gifscratcher:error: Loading or decoding failed.event.detail.errorcontains the error.
To select the final frame after loading, replace the module script in Basic Usage with this one:
<script type="module">
import { GifScratcher } from '/assets/gifscratcher/gifscratcher.js';
const target = document.querySelector('#gif-demo');
target.addEventListener('gifscratcher:ready', ({ detail }) => {
scratcher.render(detail.frames - 1);
});
target.addEventListener('gifscratcher:error', ({ detail }) => {
console.error(detail.error);
});
const scratcher = new GifScratcher(target, {
auto: false,
player: true
});
</script>
Scrub a Horizontal Sprite Strip
Keep gifscratcher.css loaded, copy dist/gifscratcher-sprite.js beside the main module, and prepare a horizontal strip of equally sized frames. The target requires a preview <img>. Set cursor: false for drag mode because the custom cursor cannot receive the pointerdown event that starts dragging.
<div id="sprite-demo">
<img src="/images/rotation-first.jpg" alt="Product rotation preview">
</div>
<script type="module">
import { GifScratcherSprite } from '/assets/gifscratcher/gifscratcher-sprite.js';
new GifScratcherSprite('#sprite-demo', {
sprite: '/images/rotation-strip.jpg',
frames: 12,
interaction: 'drag',
cursor: false,
auto: false
});
</script>
Sprite Options
sprite(string, default''): URL of the horizontal sprite image. Supply a URL to initialize the player.frames(number, default0): Number of frames in the sprite. Set a positive count matching the image strip.interaction('hover' | 'drag', default'hover'): Scrubbing mode on devices that support hover. Touch devices use press-and-move interaction.auto(boolean, defaulttrue): Starts automatic sprite playback.cursor(boolean, defaulttrue): Adds a custom cursor.speed(number, default5): Delay in animation-frame ticks for sprite autoplay. Higher values slow the animation.
Sprite Methods
destroy(): Removes listeners, observers, and the generated cursor, and clears inline styles on the sprite image. After drag or touch interaction, the target retainstouch-action: noneuntil you clear it.addCursor(): Appends the custom cursor when enabled.preload(callback?): Loads the sprite image and invokes the optional callback after loading.touchInteraction(): Attaches press-and-move handlers.hoverInteraction(): Attaches pointer-move scrubbing.dragInteraction(): Attaches press-and-drag scrubbing.auto(): Starts the automatic sprite animation loop.hover(): Attaches pointer-entry/exit and custom-cursor tracking.resize(): Updates the element’s measured dimensions and the sprite’s frame sizing.play(xcoord: number): Selects the frame at a horizontal coordinate measured from the target’s left edge, in pixels.switchFrame(frame: number): Displays a zero-based sprite frame by translating the image strip.
Create a Sprite Strip
ImageMagick can extract GIF frames and join them into a single horizontal image. Set the sprite’s frames option to the number of exported frames.
magick animation.gif -coalesce frame-%03d.jpg magick frame-*.jpg +append animation-strip.jpg
Alternatives And Related Resources
- Accessible GIF Image Player In Vanilla JavaScript – Gifa11y
- GIF Player In Pure JavaScript – LC GIF Player
- Image Sequence Player In JavaScript – img-player
- Touch-enabled 360 Image Preview In JavaScript – Threesixty
FAQs
Q: Why does a GIF from another domain fail to load?
A: The library fetches the GIF as binary data. The remote server must allow the page’s origin through CORS.
Q: Can setOptions() load another GIF?
A: No. GIF decoding happens during initialization. Call destroy(), restore the target markup, and initialize a new instance with the new gif URL.
Q: Why does the module fail when I open the HTML file directly?
A: Serve the example through a local HTTP server. ES module loading and GIF fetching are subject to browser origin restrictions.







