Your browser doesn't support some of the latest features used to build this site. For the best experience you might want to look at upgrading your browser.

Skip to main content

Autoplay HTML5 video on iOS, android and desktop

Learn how to create a more accessible, auto-playing HTML5 video which works great as a replacement for animated GIFs and component background.

A brief introduction

Before I jump into the code, let’s first look at the reasons for using HTML5 video as a replacement for animated GIFs:

  • file size
  • quality

Animated GIFs don’t compress very well. They have very large file sizes and the quality takes a massive hit as soon as we try to compress them.

.mp4 files (the file format we’ll be using for our videos), compress much better and maintain their quality. Here’s an example:

Comparison of a 13 second animated GIF and MP4
GIF MP4 Savings
4.8mb 836kb 574%

That’s a huge saving! Unfortunately, it does mean more work on our part to set up the markup needed to create an auto-playing HTML5 video.

It’s also worth noting, prior to iOS10 auto-playing HTML5 video wasn’t possible on iOS devices. But that changed when Apple implemented the playsinline attribute (which needs to be added to our <video> element to autoplay on iOS).

Tip: If your video isn’t auto-playing on iOS check that the <video> element has the playsinline attribute or that your phone isn’t on power saving mode.

Setting up the markup

With the intro covered, let’s jump into the markup needed for our videos.

<video class="m-video__video js-video-video" poster="/img/showreel.jpg" autoplay loop muted playsinline>
    <source src="/video/showreel.mp4" type="video/mp4">
</video>

Ok, let’s take a look at what all of these attributes are for:

  • poster - an image that will be shown whilst the video is loading
  • autoplay - this will cause the video to autoplay as soon as it’s loaded
  • loop - makes the video start again when it’s finished
  • muted - prevents any audio from playing (a video must be muted or have no audio for it to autoplay on iOS)
  • playsinline - this tells iOS devices to play videos inline (so the user doesn’t have to interact with it to make it play)
Tip: Set the poster attribute to an image of the first frame from the video. This creates a seamless experience when the video is loaded in place of the image.

Play/pause button markup

To make our autoplaying videos more inclusive, we’re going to add play/pause buttons, so users can pause the video if they don’t want it to play.

There are many reasons someone might want to pause a video; ranging from saving battery life to suffering from vestibular disorders.

This can cause someones vestibular system to struggle to make sense of what is happening, resulting in loss of balance, vertigo, migraines, nausea, and hearing loss.

With that covered, let’s have a look at the markup:

<button class="a-button m-video__button js-video-play-button" aria-pressed="true" aria-label="Play video" type="button">
    <svg class="a-icon a-button__icon" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
        <title>Play video</title>
        <path d="M5.85355339,0.853553391 L12.2928932,7.29289322 C12.6834175,7.68341751 12.6834175,8.31658249 12.2928932,8.70710678 L5.85355339,15.1464466 C5.65829124,15.3417088 5.34170876,15.3417088 5.14644661,15.1464466 C5.05267842,15.0526784 5,14.9255015 5,14.7928932 L5,1.20710678 C5,0.930964406 5.22385763,0.707106781 5.5,0.707106781 C5.63260824,0.707106781 5.7597852,0.759785201 5.85355339,0.853553391 Z"></path>
    </svg>
</button>

<button class="a-button m-video__button js-video-pause-button" aria-pressed="false" aria-label="Pause video" type="button">
    <svg class="a-icon a-button__icon" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
        <title>Pause video</title>
        <rect x="4" y="1" width="3" height="14" rx="1"></rect>
        <rect x="9" y="1" width="3" height="14" rx="1"></rect>
    </svg>
</button>

Ok, let’s address the elephant in the room. Why do we have a separate button to play the video and a separate button to pause the video.

You might be thinking it’d be much easier to just change the icon between states. This works great for sighted users, but not for users who rely on a screen reader.

A few key points:

  • aria-pressed - we’ll be updating this value with JavaScript to indicate if the button is pressed or not
  • aria-label - will be read out loud by screen readers
  • <title> - will display the text when a user hovers over the button, just like the title attribute does

For a more in-depth explanation, make sure to read Building Inclusive Toggle Buttons by Heydon Pickering.

Adding some style

Instead of jumping straight in, let’s progressively enhance the markup we’ve created.

First things first, let’s hide the buttons we just added if they aren’t needed:

.no-enhance .m-video__button {
    display: none;
}

By default, I’ve added a no-enhance class to the html element. This means if the browser doesn’t have JavaScript turned on or the browser doesn’t support the features we need for the JavaScript to work (we’ll look at this later), then we’ll hide the buttons as they won’t do anything.

Styling our play/pause buttons

Ok, now we can start layering more styles on to get our buttons looking how we want them.

.a-button {
    background-color: #ffffff;
    border-radius: 100%;
    cursor: pointer;
    position: relative;
    transition: box-shadow 0.3s cubic-bezier(0.5, 0.61, 0.355, 1), opacity 0.3s cubic-bezier(0.5, 0.61, 0.355, 1);
}

.a-button:focus {
    box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.5);
    outline: none;
}

.a-button:hover {
    opacity: 0.75;
}

.a-button__icon {
    padding: 0.625rem;
}

.a-icon {
    fill: #000000;
    height: 2.1875rem;
    vertical-align: top;
    width: 2.1875rem;
}

@media screen and (min-width: 32em) {
    .a-icon {
        height: 2.5rem;
        width: 2.5rem;
    }
}

Here’s the CSS we need to style up our play and pause buttons. Nothing too fancy here (if you need to support browsers further back then you can add in fallbacks for rem, inline SVG etc).

We won’t be covering that in this post, but if enough people want a solution that goes back to the prehistoric period , then I’ll update this post.

Styling our HTML5 video

We need a surprisingly little amount of CSS for our HTML5 video:

.m-video {
    background-color: #000000;
    position: relative;
}

.m-video__video {
    display: block;
    width: 100%;
}

.m-video__button {
    bottom: 0.9375rem;
    left: 0.9375rem;
    position: absolute;
    z-index: 20;
}

.m-video__button[aria-pressed="true"] {
    display: none;
}

Most of this should be pretty self-explanatory. What I will explain, however, is this block of code:

.m-video__button[aria-pressed="true"] {
    display: none;
}

What we’re doing here is hiding the play/pause button that has already been pressed.

So if the user clicks the play button the aria-pressed value will get set to true and the play button will get hidden (at least it will, when we have the JavaScript set up).

We’re on our way to having a great replacement for animated GIFs. So let’s carry on.

Hooking up our JavaScript

By now we have already created our autoplaying HTML5 video and added our play/pause buttons. The only issue is, our buttons don’t actually do anything yet.

So let’s add in some JavaScript to get this working. We’ll be making use of ES6 classes and other features in our demo.

First of all, let’s add in some script to check if the browser supports the features we need and if it does we’ll execute our video JavaScript:

/* Check if the users browser supports these features */
const enhance = 'querySelector' in document && 'addEventListener' in window && 'classList' in document.documentElement;

/* If the users browser browser supports the features we need, remove the no-enhance class from the html element and execute our video JS */
if(enhance) {
    document.documentElement.classList.remove('no-enhance');

    /* Find all video molecules and instantiate a new instance of the Video class */
    const videos = document.querySelectorAll('.js-video');
    Array.from(videos).forEach((video) => {
        const videoEl = new Video(video);
    });
}

Now that we have that setup, we can create our video JavaScript:

class Video {
    constructor(video) {
        this.videoContainer = video;
        this.video = this.videoContainer.querySelector('.js-video-video');
        this.playButton = this.videoContainer.querySelector('.js-video-play-button');
        this.pauseButton = this.videoContainer.querySelector('.js-video-pause-button');

        this.prefersReducedMotion();
        this.addEventListeners();
    }

    prefersReducedMotion() {
        /* If the users browser supports reduced motion and the user has set it to true, remove the autoplay attribute from the video and pause it */
        if(matchMedia('(prefers-reduced-motion)').matches) {
            this.video.removeAttribute('autoplay');
            this.pauseVideo();
        }
    }

    addEventListeners() {
        this.playButton.addEventListener('click', () => {
            this.playVideo();
            /* Focus the pause button so keyboard users can immediately pause the video without having to tab away and back again */
            this.pauseButton.focus();
        });

        this.pauseButton.addEventListener('click', () => {
            this.pauseVideo();
            /* Focus the play button so keyboard users can immediately play the video without having to tab away and back again */
            this.playButton.focus();
        });
    }

    playVideo() {
        this.video.play();
        /* Set the play button as pressed so it's hidden and the pause button is displayed instead */
        this.playButton.setAttribute('aria-pressed', 'true');
        this.pauseButton.setAttribute('aria-pressed', 'false');
    }

    pauseVideo() {
        this.video.pause();
        /* Set the pause button as pressed so it’s hidden and the play button is displayed instead */
        this.playButton.setAttribute('aria-pressed', 'false');
        this.pauseButton.setAttribute('aria-pressed', 'true');
    }
}

I’ve added comments to the JavaScript so it should be pretty clear what’s going on.

That being said, I will expand on the prefersReducedMotion() method.

What is prefers reduced motion?

If this is the first time you’ve heard of the prefers-reduced-motion query; Val Head sums it up perfectly, in her post: The reduced motion query at a glance.

Essentially, we can tone down or remove motion that may be harmful to those with vestibular disorders or motion-sensitivities.

Currently, this feature is only supported in Safari, but will hopefully be adopted by other browsers sometime soon.

Prefers reduced motion browser support

Mac: how to turn on reduced motion

If you have a mac you can turn on reduced motion by following these steps:

  1. Open System Preferences
  2. Click on Accessibility
  3. Click on Display
  4. Check the Reduce Motion checkbox

iOS: how to turn on reduced motion

If you have an iOS device (iPhone/iPad), you can turn on reduced motion by following these steps:

  1. Open Settings
  2. Tap on General
  3. Tap on Accessibility
  4. Tap on Reduce Motion
  5. Toggle on Reduce Motion

Fin

There you have it, an inclusive, progressively enhanced replacement for animated GIFs using HTML5 video.

See the Pen Autoplay HTML5 video (animated GIF replacement) by Rob Simpson 👨🏻‍💻 (@robsimpson) on CodePen.

If you’ve enjoyed this post I’d really appreciate it if you shared it around online .

Equally, if you have any questions don’t hesitate to drop me a message on social media .

Thank you, your answers have been sent
These will help me create more relevant content that you’re looking for.
Which topics would you like to see more of?
Thank you, your answers have been sent
These will help me create more relevant content that you’re looking for.
Always stay up to date
What would you like to recieve?
Please confirm that you’d like to hear from me

I’ll only use the information you provide on this form to get touch about the topics you’ve chosen above. For more information on how I use and protect your data, please view my privacy policy.

Related tutorials