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

Find out how to create inclusive, progressively enhanced, autoplaying HTML5 videos as a replacement for animated GIFs and component backgrounds.

View demo View code

Table of contents

  1. A brief introduction
  2. Setting up the markup
    1. Play/pause button markup
  3. Adding some style
    1. Styling our play/pause buttons
    2. Styling our HTML5 video
  4. Hooking up our JavaScript
    1. What is prefers reduced motion?
  5. Fin

A brief introduction

Before I jump into the code, I want to explain the reason for using HTML5 video as a replacement for animated GIF’s; file size.

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

.mp4 files (which we’ll be using), on the other hand, 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%

Up until iOS10, autoplaying HTML5 video wasn’t possible on iOS devices (largely because of the intrusive behaviour of autoplaying videos and battery consumption).

But Apple realised the performance benefits of using HTML5 video as a replacement for animated GIF’s and implemented the playsinline attribute (which we’ll look at later).

FYI: Videos won’t autoplay on iOS devices if it’s on power saving mode. (I’m not sure about Android devices, but if you have one can you let me know).

Setting up the markup

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

FYI: I use the Atomic Design methodology throughout my HTML, CSS and JS. You’ll see my class names using a mashup of BEM and Atomic Design; also known as ABEM.
<video class="m-video__video js-video-video" poster="/img/showreel.jpg" autoplay loop muted playsinline>
    <source src="/video/showreel.mp4" type="video/mp4">

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="">
        <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>

<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="">
        <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>

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) {

    /* 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.videoContainer.querySelector('.js-video-video');
        this.playButton = this.videoContainer.querySelector('.js-video-play-button');
        this.pauseButton = this.videoContainer.querySelector('.js-video-pause-button');


    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) {

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

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

    playVideo() {;
        /* 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() {;
        /* 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

How to turn on reduced motion: Mac

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

How to turn on reduced motion: iOS

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


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

You can extend this further, to create component backgrounds, like I’ve done in the demo.

If you’ve enjoyed this post I’d really appreciate it if you shared it around online (only if you want to of course ).

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

If you suffer from a disability and have noticed any issues with this tutorial, please drop me an email. This will help me to improve current and future tutorials to make them more inclusive.

I am colour blind; I suffer from Deuteranopes. Now that you know something about me, I hope you don’t feel shy about getting in touch.

Comments (1)

Post update, chrome on android will autoplay the video even when on power saving mode.
Leave a comment