The Case Against the GIF

The word “GIF” survives in everyday language, but the format itself is a relic. When Twitter’s “GIF” button inserts an MP4 inside a <video> element rather than an actual .gif file, it’s a clear signal there’s no longer a good reason to use the bulky legacy format. The question is what to use instead.

Playing Video Like a GIF

Recreating the GIF experience—autoplay, looping, no sound—with the <video> element is straightforward:

<video autoplay loop muted playsinline src="cats.mp4"></video>

The playsinline attribute ensures mobile browsers play the video in place rather than launching fullscreen. While the <video> element itself has universal support, the same can’t be said for video formats. A video file combines a container (the file type, like MP4 or WebM) with one or more codecs (the compression method, like H.264 or VP9). A browser must support both the container and the codec to play the file.

Browser support for the various combinations is uneven. MP4 with H.264 is the safe, universally supported choice. WebM works in all browsers except iOS Safari. Newer codecs offer better compression at the cost of coverage: VP9 follows WebM support, HEVC is in Safari, Edge, and Chrome (from version 105), and AV1—the royalty-free codec from the Alliance for Open Media—has been in Chrome and Firefox since 2018 and 2019, respectively, but still hasn’t shipped in Edge or Safari. That gap matters less as AV1 gains momentum with Netflix, YouTube, and Vimeo; Apple’s membership in the Alliance suggests full support is only a matter of time.

To serve the best format a browser will accept, list multiple <source> elements in order, with the preferred format first:

<video autoplay loop muted playsinline>
  <source src="cats.webm" type="video/webm"> <!-- ideal -->
  <source src="cats.mp4" type="video/mp4"> <!-- fallhack -->
</video>

Differentiating between formats inside the same container requires the codecs parameter in the type attribute. The syntax is notoriously unwieldy—an AV1 entry looks something like this:

<video autoplay loop muted playsinline>
  <source src="cats.mp4" type="video/mp4; codecs=av01.0.05M.08" >
  <source src="cats.mp4" type="video/mp4" >
</video>

The AV1 codecs value always starts with av01, followed by a profile number (0, 1, or 2), a two-digit level, and a tier letter (M or H). If you want to skip that complexity, you can put your AV1 video in a WebM container and omit the codec parameter entirely.

Most video editing tools don’t export WebM or AV1. The workaround is to export to an intermediate format like .mov and convert with FFmpeg:

ffmpeg -i yourSourceFile.mov -map_metadata -1 -c:a libopus -c:v librav1e -qp 80 -tile-columns 2 -tile-rows 2 -pix_fmt yuv420p -movflags +faststart -vf &quot;scale=trunc(iw/2)*2:trunc(ih/2)*2&quot; videoTitle.mp4

Start from the highest-resolution source available. Converting a .gif still yields massive file-size savings, but the final quality will be limited by the source:

ffmpeg -i cats.gif -map_metadata -1 -an opus -c:v librav1e -qp 80 -tile-columns 2 -tile-rows 2 -pix_fmt yuv420p -movflags +faststart -vf &quot;scale=trunc(iw/2)*2:trunc(ih/2)*2&quot; cats.mp4

The <video> element has real drawbacks, though. It can’t be styled with CSS filters in the same way as an image, and using it as a full-screen background requires more markup and positioning code:

.video-parent {
  position: relative;
  width: 100vw;
  height: 100vh;
} 

.video-parent video {
  object-fit: cover;
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
}

More critically, a looping <video> on the page can prevent a user’s screen from going to sleep, a side-effect that <img> does not trigger.

Browsers also handle <video> loading inefficiently for short clips. Preloaders skip <video> content because it could be long-form, so loading is delayed until the main thread parses it. And browsers that assume long-form content issue a 1-byte request to test for HTTP Range Requests, followed by multiple range requests of varying chunk sizes—several round trips before the first frame can decode. On high-latency connections, that adds hundreds or thousands of milliseconds of delay.

Animated Images: WebP and AVIF

Animated image formats bring the convenience of the <img> element—native lazy-loading, familiar styling, and simpler positioning as a background-image—along with modern compression.

Animated WebP has broad support (Chrome, Firefox, Edge, and Safari since version 14). AVIF, the image counterpart to AV1, is newer and more performant, frequently shrinking GIFs by over 90%. For most web use cases, it’s now the best image format available. Unlike a video file, animated AVIF can be treated as an image, meaning you can apply a shadow with plain CSS:

filter: drop-shadow(2px 4px 6px black);

Support for AVIF animation is still spotty: Chrome, Samsung Internet, and Safari (from version 16.1) handle it, while Firefox supports only still AVIF images. That partial support creates a trap for progressive enhancement, because Firefox would receive the AVIF and display it as a static frame rather than falling back to an animated WebP:

<picture>
  <source type="image/avif">
  <img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f4a9/512.webp" alt="💩" width="32" height="32">
</picture>

The <picture> element does open up opportunities that <video> can’t match without scripting—showing different animations for light vs. dark mode:

<picture>
  <source media="(prefers-color-scheme: dark)">
  <img src="light-animation.avif" alt="">
</picture>

And serving different aspect ratios or crops based on screen orientation or viewport size via a media query:

<picture>
  <source type="image/avif" media="(orientation: landscape)"">
  <img src="typeloop-portrait.avif" alt="">
</picture>

These outcomes are theoretically possible with <video> using JavaScript and matchMedia, but a markup-first solution is preferable where one exists.

Tooling for animated AVIF and WebP currently lags. There’s no direct export from video editing software; you’ll need FFmpeg, ezgif.com, or a service like Cloudinary to convert. Squoosh does not yet support animated AVIF, and design tools aren’t keeping up—Figma plays animated GIFs in prototypes but can’t import or export even a still AVIF.

Videos in an <img> Tag (Safari Only)

Safari 11.1 shipped an unusual capability: accepting a video file as the source of an <img> element.

<img src="cat.mp4" alt="A Siamese cat walking in a circle">

In Safari, this also means video files work in any CSS property that accepts an image, including background-image and border-image:

.video-border {  
  border: 40px solid transparent;
  border-image: url(abstract_bg_animation.mp4) 100 round;
}

Apple has claimed performance benefits over both GIFs and the <video> element:

By placing your videos in <img> elements, the content loads faster, uses less battery power, and gets better performance.

A bizarre follow-on: the poster attribute of a <video> element can itself be a video, and it will autoplay even if the user has blocked video autoplay. Chrome filed this under “WontFix,” and there’s an open issue to add the behavior to the HTML spec, which would force the issue.

Respecting User Preferences and Accessibility

A <video> element automatically honors a user’s decision to block autoplaying or animated content in Firefox and Safari. Users can still choose to play a specific video from the context menu, or enable autoplay for a site. With an animated image, that choice disappears entirely.

Autoplaying animation that lasts more than five seconds must be pausable to meet WCAG Success Criterion 2.2.2 (Pause, Stop, Hide). The <video> element satisfies that requirement without extra work, since users can right-click and pause via the available menu even when controls are hidden.

The prefers-reduced-motion media query lets you respect broader system settings. In a <picture> context, you can swap the animation for a still image:

<picture>
  <source
   
    type="image/avif"
    media="(prefers-reduced-motion: no-preference)"
  />
  <img src="nyancat.png" alt="Nyan cat" width="250" height="250" />
</picture>

The tradeoff is that this gives users no way to opt back in—someone who prefers less animation, but not none, can’t choose to watch the animated version without changing their system setting. A <video> element gives those users their choice back.

Lottie

For vector-heavy, GUI-authored animation, Lottie is an established option. It’s an open-source library (via Bodymovin, an After Effects extension) that plays animation data stored as JSON. A minimal integration looks like this:

<div id="lottie"></div>
const animation = bodymovin.loadAnimation({
  container: document.getElementById('lottie'),
  path: 'myAnimation.json',
  renderer: 'svg',
  loop: true,
  autoplay: true,
})

Playback can be triggered on specific events instead of page load:

const lottieContainer = document.getElementById('lottie');
const animation = bodymovin.loadAnimation({
  container: lottieContainer, 
  path: 'myAnimation.json',
  renderer: 'svg',
  loop: true,
  autoplay: false,
  })
// Play the animation on hover
lottieContainer.addEventListener('mouseover', () => {
  animation.play();
});
// Stop the animation after playing once
animation.addEventListener('loopComplete', function() {
  animation.stop();
});

Lottie’s costs include more than the animation file. The web library itself runs 254.6KB (63.8KB gzipped), and animations rendered as SVG can create a heavy DOM. Rendering to <canvas> avoids the DOM bloat but requires a different version of the JavaScript library.

Lottie is not a universal GIF replacement—it’s for layered 2D graphics, not video-based content. And despite exporting from a GUI tool, it loses the “zero-JavaScript” benefit of an image tag. For a one-off effect, an AVIF file will frequently outperform pulling in an entire animation library.

There’s no single successor to the GIF. Modern codecs and containers each have their sweet spots, and the right tradeoff between file size, browser support, developer convenience, and user control depends entirely on your specific use case.