How to convert HTML to GIF

A GIF of a page is a recording, not a conversion. You capture frames while the page runs and encode them. The colour quality depends almost entirely on one ffmpeg step most people skip.

To convert HTML to GIF you record the page while it runs and encode the frames. There is no single pass conversion, because a GIF is a sequence of images and a page is one document.

That means two decisions before you start. How you capture the frames, and how the colours get reduced to the 256 that GIF allows.

A screen recorder window framed over a browser tab, with the capture region drawn around the animated card.
A screen recorder window framed over a browser tab, with the capture region drawn around the animated card.

Two ways to convert HTML to GIF

Route Setup Framing Best for
Screen recorder Install one app You drag a box A one-off clip of a live page
Headless browser frames Node, a browser, ffmpeg Exact pixels Repeatable output, clean edges

If this is the only GIF you will make this month, use a recorder. If it is part of a documentation pipeline, capture frames.

Route 1: a screen recorder

Open the page in a browser, start the recorder, frame the region, and let the animation run once or twice. Most recorders export GIF directly.

Three settings decide whether the result is usable.

  • Frame rate. 12 to 15 is enough for interface motion. 30 doubles the file for little visible gain.
  • Region. Crop tight to the element. Recording the whole browser window wastes most of the pixels on chrome and background.
  • Loop point. Start and end on the same visual state, or the loop will jump.

Hide the cursor unless the point is where you clicked. A stray pointer in the corner makes the clip look unplanned.

Route 2: capture frames in a headless browser

This produces frames at exact dimensions with no recorder artefacts, and it reruns the same way every time.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 800, height: 450 } });
await page.goto('file:///C:/pages/animation.html');
await page.waitForLoadState('networkidle');

for (let i = 0; i < 45; i++) {
  await page.screenshot({ path: `frames/f${String(i).padStart(3, '0')}.png` });
  await page.waitForTimeout(66);
}
await browser.close();

Forty five frames at roughly 66 milliseconds gives about three seconds at 15 frames per second. Adjust both numbers together.

This loop does not guarantee even spacing, because each screenshot takes time. For motion that must be smooth, drive the animation by stepping it explicitly rather than by waiting.

The palette step that fixes the colour

GIF stores at most 256 colours per frame. With a generic palette, gradients band and anti aliased text gets a halo.

The fix is two ffmpeg passes. The first builds a palette from your actual frames; the second encodes using it.

ffmpeg -framerate 15 -i frames/f%03d.png -vf palettegen palette.png

ffmpeg -framerate 15 -i frames/f%03d.png -i palette.png \
  -lavfi "paletteuse=dither=bayer:bayer_scale=3" out.gif

The difference is large on any page with a gradient or a shadow. Run both passes; skipping the first is the single most common cause of a GIF that looks worse than the page.

The same three frames encoded with a generic palette and with a generated palette, side by side.
The same three frames encoded with a generic palette and with a generated palette, side by side.

Keeping the file small

GIF has no interframe compression worth the name, so size scales with frames multiplied by area.

  1. Cut the duration first. Two seconds that show the point beat eight that show it slowly.
  2. Then the frame rate. 15 is usually indistinguishable from 30 for interface motion.
  3. Then the width. Halving the width quarters the pixel count.
  4. Only then the colours. palettegen=max_colors=128 trades visible quality for size.

A GIF above about five megabytes will be rejected or silently compressed by several chat and forum platforms.

The order matters because each step costs something different. Duration costs content, frame rate costs smoothness, width costs legibility, and colour depth costs appearance everywhere at once.

Cropping tighter is the one change that costs nothing. A clip framed around the card rather than the browser window is often half the size with the same information in it.

If the clip is a demonstration of an interface, hide anything that is not part of the point before recording. Empty sidebars and background panels are pixels you pay for in every frame.

When MP4 is the better answer

For anything longer than a few seconds, MP4 is smaller by an order of magnitude at the same visual quality, and it plays inline in most places now.

The cases where GIF still wins are narrow: autoplay with no player controls, embedding in an email client, and platforms that treat GIF as an image rather than media.

Converting HTML to MP4 covers the recording route for video, which uses the same frame capture and a different encode.

When motion is not what you need

A surprising number of GIF requests turn out to be a still image request. If nothing moves in the page, capture a PNG and the file will be smaller and sharper.

If the page has a chart that people will want to hover over, or a table they will want to sort, neither a GIF nor a PNG carries that. A recording of an interaction is not the interaction.

A static card captured as PNG next to the same card recorded as a GIF, with the file sizes shown.
A static card captured as PNG next to the same card recorded as a GIF, with the file sizes shown.

Sending the page as well as the clip

The usual arrangement is a short GIF so the message shows motion, and a link so the real thing is one click away.

Paste the HTML into a NOS document and it renders as written, scripts and animation included, at its own address. The reader sees the live page rather than a recording of it.

Turning HTML into a link is that step alone. After that, fixing the page does not mean re-recording the clip, because the link already points at the current version.

Questions people ask

How do I turn an HTML animation into a GIF?

Record the page while it animates, then encode the frames. A screen recorder does this in one pass. A headless browser gives you cleaner output by capturing numbered frames and handing them to ffmpeg.

Why does my GIF look grainy and banded?

GIF allows only 256 colours per frame. Without a generated palette the encoder uses a generic one and dithers heavily. Run ffmpeg palettegen and paletteuse in two passes and the banding largely disappears.

Why is the GIF file so large?

Frame rate and dimensions drive size far more than duration. Drop to 12 or 15 frames per second, halve the width, and trim to the seconds that matter. GIF has no interframe compression comparable to video.

Should I use MP4 instead?

For anything over a few seconds, yes. MP4 is far smaller at the same quality and plays inline in most places now. GIF still wins where autoplay looping with no player controls is required.

Keep reading