Responsive Ambient Videos with AV1 and HEVC/H.265 Compression

Ambient videos, or "short looping videos as a background element," are both common and tricky to get right. They can add a lot of visual texture to a design, but are often large, chunky elements that can slow down your performance.

But this is 2026! We have some new tools to help with video compression! We're going to utilize some of the techniques Evil Martians demonstrated with AV1 encoding and combine them with responsive video techniques that Scott Jehl explains well over on his blog.

There are a few assumptions I'm making: you will be processing the video manually, and you have ffmpeg installed (I recommend Homebrew on a Mac).

At the end of this, we'll have:

We'll tie it all together in an HTML <video> element and implement some accessibility features.

Disclaimer

But first... a disclaimer:

No amount of compression is going to make a 180+ second video performant as an ambient video! Optimization starts at the communication level.

If an ambient video is being considered, consider the following guidelines:

Once we have a reasonable starting point, we can work on smooshing these down to a nice size.

AV1

AV1 is our champion video compression codec that does some smart things to reduce filesize. But as with all nice, new things, it's not fully supported yet.

To make an AV1 from our reasonable starting point, we can run the following:

ffmpeg -i INPUT.mov -map_metadata -1 -an -c:v libsvtav1 -qp 40 -tile-columns 2 -tile-rows 2 -pix_fmt yuv420p -movflags +faststart -vf scale=-1:1080 output.av1.1080.mp4

Here's a breakdown of what's happening:

You will likely need to adjust the -qp settings and your output resolutions to get the right mix.

HEVC/H.265

We still need a fallback, but we don't have to go all the way back to H.264. Safari supports the HEVC/H.265 codec, which is like H.264 but ... sleeker. 30-50% smaller than H.264 (with AV1 being 30% smaller than HEVC).

This one is going to take much longer, so grab some coffee and run:

ffmpeg -i INPUT.mov -map_metadata -1 -an -c:v libx265 -crf 28 -preset veryslow -pix_fmt yuv420p -movflags +faststart -tag:v hvc1 -vf scale=-1:1080 video.hevc.mp4

Many of the same flags appear as before, but there are a few new ones:

Again, you may need to adjust your -crf rate to get the right amount of compression and quality. The output resolution should match the AV1 file.

Small Desktop / Tablet Versions

To generate smaller videos, reuse the same commands from before, but change your scaling command. Something like:

-vf scale=-1:720

This will output videos that are 720 pixels tall.

If your video is a weird size, you may get errors if the numbers don't divide cleanly when scaling down. If that happens, choose a new size or run the math on your own to find what numbers are going to work.

Mobile Versions

Most likely, your starting video will be in a wide 16:9 format, but that's a good amount of pixels you'll never see on a mobile screen! We're going to make a vertical crop of the video to minimize our wasted pixels.

ffmpeg -i INPUT.mov -vf "crop=ih*9/16:ih" -c:a copy output.vertical.mp4

Now that we have a vertical file, we can make AV1 and HEVC/H.265 videos like the previous steps. Since we have less overall data, you can likely get away with something like -vf scale=-1:1620 to give those high-density pixel displays a nice video while still being small.

Making a poster image

We also need a static poster image that will display if the video is loading over slow connections:

ffmpeg -i INPUT.mov -ss 00:00:00.000 -vframes 1 thumb.png

You'll want to size and compress that image into a reasonable format (WEBP or AVIF). Smaller is better here.

Rendering the video

Now that we have all the pieces, we can load them into a video element:

<video autoplay playsinline muted loop
  poster="/assets/video/output.poster.avif">
  <source
    src="/assets/video/output.vert.av1.1280.mp4"
    media="(max-width: 769px)" type="video/mp4;
    codecs=av01.0.05M.08,opus"
  />
  <source
    src="/assets/video/output.vert.hevc.1280.mp4"
    media="(max-width: 769px)"
    type="video/mp4; codecs=hvc1"
  />
  <source
    src="/assets/video/output.av1.720.mp4"
    media="(max-width:1441px)"
    type="video/mp4; codecs=av01.0.05M.08,opus"
  />
  <source
    src="/assets/video/output.hevc.720.mp4"
    media="(max-width:1441px)"
    type="video/mp4; codecs=hvc1"
  />
  <source
    src="/assets/video/output.av1.1080.mp4"
    type="video/mp4; codecs=av01.0.05M.08,opus"
  />
  <source
    src="/assets/video/output.hevc.1080.mp4"
    type="video/mp4; codecs=hvc1"
  />
</video>

Ensure these attributes are on your <video> element:

And in addition to src on our <source> elements, we set:

Note that I used max-width queries, but you could just as easily build them with min-width queries; just be aware that the browser will use the first item that matches.

A11Y Considerations

We have an ambient video, but there's still some accessibility we need to ensure. There are two features we need to enable.

The WCAC SC 2.2.2 "Pause, Stop, Hide" criteria states that something longer than 5 seconds (which we likely qualify for) needs some sort of control to stop the motion. The simplest implementation is a play/pause toggle button.

How you enable it will vary on how you're handling interactivity on your project, but a very simple Vanilla JavaScript implementation could look like:

// the awesome button you made to toggle the video
const videoToggleButton = document.querySelector("your-button-selector-here");

// the video itself
const video = document.querySelector("your-video-selector-here")

// toggle the play state of the video
const handleVideoToggle = () => {
  video?.paused ? video?.play() : video?.pause();
  // also whatever you're doing to toggle the play/pause icons
}

// listen for clicks
videoToggleButton?.addEventListener("click", handleVideoToggle);

This is over-simplified, but you get the idea. Change the selector to whatever you're using to identify your elements.

Also, unless your video is extremely subtle, we need to prevent it from autoplaying if users have prefers-reduced-motion set. You COULD hide it with a CSS media query, but 'reduced motion' does not mean 'no motion'. I prefer that users have the option to still engage with it as they want.

// Check if the prefers-reduced-motion media query matches 'reduce'
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

// grab the video if you haven't already
const video = document.querySelector("your-selector-here")

if (prefersReducedMotion && video) {
  this.video?.pause();
  this.video?.removeAttribute("autoplay");
  // also whatever you're doing to toggle the play/pause icons
}

And there we have it: a responsive, accessible, highly compressed ambient video!

Newsletter

Want to receive these thoughts and others in your inbox?

Discussion

Want to discuss this? Email Me