How to Pause an Animated GIF with CSS and JavaScript
This article provides a practical guide on how web developers can
implement user-controlled pause and play functionality for animated GIFs
using HTML5, CSS, and JavaScript. Because the standard GIF format lacks
a native playback control API, developers cannot simply call a pause
method on an image element. Instead, pausing must be simulated either by
swapping the animated file with a static image or by freezing the
current frame dynamically onto an HTML5 <canvas>
element.
The Challenge with Native GIFs
Browsers treat animated GIFs as standard images rendered via the
<img> tag. Unlike HTML5 <video> or
WebM formats, standard image tags do not support methods like
play() or pause(). To give users control—an
essential feature for accessibility and preventing
distractions—developers must capture and display a frozen state of the
animation on demand.
Method 1: The Canvas Freeze Technique (Dynamic Pause)
The most seamless approach captures the exact frame displayed when
the user triggers the pause action, rendering it onto a hidden
<canvas> layered directly over the GIF.
1. HTML Structure
Wrap the image, a canvas element, and a control button in a container:
<div class="gif-container">
<img id="animated-gif" src="animation.gif" alt="Animated illustration" crossOrigin="anonymous">
<canvas id="gif-canvas"></canvas>
<button id="toggle-btn" aria-label="Pause animation">Pause</button>
</div>2. CSS Styling
Ensure the canvas directly overlaps the image, remaining hidden until the pause state is active:
.gif-container {
position: relative;
display: inline-block;
}
#gif-canvas {
position: absolute;
top: 0;
left: 0;
display: none;
pointer-events: none;
}
.paused #gif-canvas {
display: block;
}3. JavaScript Implementation
Use JavaScript to draw the current frame to the canvas when paused, and clear it to resume playback:
const container = document.querySelector('.gif-container');
const img = document.getElementById('animated-gif');
const canvas = document.getElementById('gif-canvas');
const button = document.getElementById('toggle-btn');
const ctx = canvas.getContext('2d');
let isPaused = false;
button.addEventListener('click', () => {
if (!isPaused) {
// Set canvas dimensions to match the image
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
// Draw the current frame of the GIF onto the canvas
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Show canvas overlay
container.classList.add('paused');
button.textContent = 'Play';
button.setAttribute('aria-label', 'Play animation');
isPaused = true;
} else {
// Hide the canvas overlay to show the underlying animation
container.classList.remove('paused');
ctx.clearRect(0, 0, canvas.width, canvas.height);
button.textContent = 'Pause';
button.setAttribute('aria-label', 'Pause animation');
isPaused = false;
}
});Note: Due to how GIFs run in the browser rendering pipeline, resuming the GIF will not continue from the exact millisecond it stopped; rather, it will reveal the GIF at its current internal animation cycle.
Method 2: The Source Swap Technique (Static Image)
If capturing the exact frame is unnecessary, swapping between a static image (such as a JPEG or PNG of the first frame) and the animated GIF offers a lightweight alternative.
1. HTML with Data Attributes
Store both sources in custom data attributes:
<img
id="toggle-gif"
src="animation.gif"
data-animated="animation.gif"
data-static="static-frame.png"
alt="Demonstration graphic"
>
<button id="swap-btn">Pause</button>2. JavaScript Toggle
Update the src attribute when the user toggles the
button:
const gif = document.getElementById('toggle-gif');
const swapBtn = document.getElementById('swap-btn');
swapBtn.addEventListener('click', () => {
const isPlaying = gif.src.endsWith(gif.dataset.animated);
if (isPlaying) {
gif.src = gif.dataset.static;
swapBtn.textContent = 'Play';
} else {
gif.src = gif.dataset.animated;
swapBtn.textContent = 'Pause';
}
});Supporting User Accessibility Preferences
To respect users who have configured their operating systems to
minimize non-essential motion, incorporate the CSS
prefers-reduced-motion media query. Developers can combine
this media query with JavaScript to default GIFs to their paused or
static state automatically:
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Automatically activate the paused state on initial load
button.click();
}