12k
All articles

How to Replace Animated GIFs With MP4 Video

Replace animated GIFs with MP4 or WebM video using autoplay muted loop playsinline markup, plus ffmpeg conversion and LCP tips.

OpenReplay Team
OpenReplay Team
How to Replace Animated GIFs With MP4 Video

To replace an animated GIF with video, convert the clip to an H.264 MP4 and a VP9 WebM, then embed both with <video autoplay muted loop playsinline>, listing the WebM <source> first.

If Lighthouse has flagged your product page or docs for animated content, the cause is usually a screen-recording GIF weighing several megabytes. This article walks through why GIF loses on size, the conversion (browser tool or ffmpeg, your choice), the exact markup, what the swap does to LCP, and where GIF is still the right call.

Key Takeaways

  • An animated GIF stores each frame as a near-complete image with at most 256 colours, so it gets none of the interframe compression video codecs are built on.
  • muted is what makes browsers permit autoplay at all, and playsinline is what stops iOS from forcing the video into fullscreen.
  • Browsers play the first <source> they can decode, not the smallest one, so the WebM source must come before the MP4 fallback.
  • Since Chrome 116, a <video> with no poster can count as the LCP element, and the time recorded is the moment its opening frame reaches the screen.
  • The size saving depends entirely on the clip; measure your own before and after rather than trusting a quoted percentage.

Why Is GIF the Wrong Container for Motion?

An animated GIF stores every frame as a nearly complete image with a palette of at most 256 colours per frame, so it gets none of the interframe compression that video formats were designed around, and it decodes in software while H.264 and VP9 have hardware decode paths on most devices. The GIF89a specification says plainly that the format was not meant to carry animation and allows it only in a limited way; looping came later, added by browsers. That is the whole history lesson you need.

The practical consequence is that a few seconds of screen recording routinely lands in the multi-megabyte range as a GIF and in the hundreds of kilobytes as video. Lighthouse surfaces this: the GIF-to-video advice now sits inside the Improve image delivery insight from Lighthouse 13 onwards.

One Real Conversion, With Real Numbers

Google’s web.dev guide on this topic publishes one honest example, converted with the same commands shown below: a 3.7 MB source GIF became a 551 KB MP4 and a 341 KB WebM. The saving depends entirely on the clip’s content, frame rate, and dimensions, so a percentage measured on one file does not generalise to yours.

FileSize
Source GIF3.7 MB
MP4 (H.264, CRF 25)551 KB
WebM (VP9, CRF 41)341 KB

Treat those numbers as one data point, not a rule. High-motion footage compresses differently from a mostly-static terminal recording. Run your own clip through the steps below and compare byte counts before you commit.

How to Replace a GIF With Video: Making the Files

You need two files: an MP4 for universal playback and a WebM that is usually smaller. Each step has a browser tool and an equivalent ffmpeg command.

Step 1: GIF to MP4. Use the GIF to MP4 converter, which runs locally in your browser, or ffmpeg:

ffmpeg -i input.gif -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" \
  -vcodec libx264 -pix_fmt yuv420p -b:v 0 -crf 25 -f mp4 output.mp4

-pix_fmt yuv420p keeps the file playable everywhere, and CRF runs 0 to 51, lower meaning higher quality, with -b:v 0 turning off the bitrate limit in CRF mode. The crop filter is web.dev’s workaround for libx264 rejecting odd pixel dimensions.

Step 2: the WebM file, for the second <source>. In the browser, feed the MP4 you just made into the MP4 to WebM converter. With ffmpeg, encode straight from the original GIF instead, so the clip is not compressed twice:

ffmpeg -i input.gif -c:v libvpx-vp9 -b:v 0 -crf 41 output.webm

VP9’s CRF scale differs from x264’s, which is why 41 here is a sane default rather than a low-quality setting.

Step 3: Optional tuning. If the result is still heavy, shrink it by resolution and quality with the video compressor, or with ffmpeg:

ffmpeg -i output.mp4 -vf scale=640:-2 -crf 28 -movflags faststart smaller.mp4

-movflags faststart moves the MP4 metadata to the front of the file so playback can begin before the download finishes.

The Markup That Behaves Like a GIF

To make a video behave like a GIF, use <video autoplay muted loop playsinline>: muted is what makes browsers permit autoplay at all, and playsinline is what stops iOS from forcing the video into fullscreen.

<video autoplay muted loop playsinline width="640" height="360">
  <source src="clip.webm" type="video/webm">
  <source src="clip.mp4" type="video/mp4">
</video>

Chrome’s autoplay policy lets a muted video start on its own, and holds back autoplay with sound until the visitor has engaged with the site. WebKit’s video policy for iOS allows videos to autoplay without a gesture only when they are muted or carry no audio track, and iPhone needs playsinline to play the clip in place. Source order matters because browsers do not pick the best <source>; they play the first one they can decode, so the smaller WebM goes first. That order is safe everywhere: caniuse’s WebM table shows full support in Safari 16 on macOS and in Safari on iOS from 17.4, so 2018-era warnings about Apple and WebM no longer apply. The width and height attributes reserve layout space, the same CLS courtesy you would give an image.

When one of these attributes goes missing, the failure is silent: no console error, no broken-image icon, just a frozen first frame. Session replay shows the page as each user actually saw it, which is the only reliable way to catch an autoplay video that never started in production.

What Does the Swap Do to Largest Contentful Paint?

Swapping <img> for <video> changes which element can be your LCP candidate. Older guidance said a <video> without a poster is invisible to LCP, but that changed in Chrome 116: the Chromium metrics changelog records that a video element is now eligible in the same way an image is, with its timestamp taken from the moment the opening frame reaches the screen. So do not add a poster just to game the metric; an autoplaying video paints its first frame immediately, and the poster image would never be seen. If your hero animation is the largest element, its LCP timing now rides on how fast that first frame arrives, one more reason to keep the files small.

When Does a GIF Still Win?

A GIF still wins where you cannot control the markup: chat messages, email clients, and GitHub READMEs render a GIF inline but will not embed an autoplaying video. In those environments, portability beats page weight, and the right move is lossy GIF compression rather than conversion. The gifsicle --lossy option (formerly the separate giflossy project) trades artifacts for size:

gifsicle -O3 --lossy=80 -o smaller.gif input.gif

Higher lossiness values permit more artifacts and smaller files; tune until the output stops looking acceptable, then back off.

Wrapping Up

On any page whose markup you control, motion belongs in a <video> element with two sources, not in a GIF. Pick the heaviest GIF on your site, run it through the conversion steps above, and compare the byte counts yourself; then ship the four-attribute markup with WebM first and check the replay of a real session to confirm it actually plays.

FAQs

Can I serve only an MP4 and skip the WebM file?

Yes. An H.264 MP4 in yuv420p pixel format plays in every modern browser, so a single-source video element works everywhere and nothing breaks. The WebM version is a size optimization, not a compatibility requirement: VP9 usually produces a smaller file at similar quality. Ship the MP4 first, then add the WebM source above it later if the page weight still bothers you.

Why does my video still not autoplay even with autoplay, muted, loop, and playsinline set?

A user agent policy is blocking playback, not your markup. iOS Safari suspends autoplay when the device is in Low Power Mode and overlays a play button instead, and battery or data saving modes in other browsers can behave similarly. Detect it in JavaScript: video.play() returns a promise, so handle the rejection by showing controls or a static fallback image instead of a frozen frame.

Does loading='lazy' work on the video element?

In Chromium-based browsers, yes. Chrome, Edge, and Opera defer a lazy video's download, poster fetch, and autoplay until the element nears the viewport, and MDN documents the attribute. A matching addition to the HTML specification is in progress, and Firefox and WebKit have both given the feature a positive standards position, but neither has shipped it yet. Browsers without support simply ignore the attribute and load eagerly, so adding it to offscreen GIF-replacement videos is safe today.

Is animated WebP a good middle ground between GIF and video?

Sometimes. Animated WebP works inside a plain img element, supports 24-bit color instead of GIF's 256-color palette, and usually produces smaller files than the equivalent GIF. But it does not match a real video codec: H.264 or VP9 compresses a screen recording far better. Use animated WebP where the environment requires an image tag but accepts modern formats, and use video wherever you control the markup.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.