Pausing GIF animations on dev.to for those who `prefer-reduced-motion` [hack 1]
Tags: javascript, a11y, inclusion, webdev
Published: 2021-03-25 10:14:43

Animated GIFs can be problematic, maybe we can find a way of turning them "off" for people who don't want to see them?

**Word of warning:** This is barely tested and may break, it is a "proof of concept" as something the dev.to team could do to fix the [animated GIFs issue](https://github.com/forem/forem/issues/946) temporarily while they work on the full fix.
___

## Contents
 - [Animated GIFs are awesome, why would anyone not want to see a loop of my cat falling off a shelf](#animated)
&nbsp;

 - [The Code](#code)
&nbsp;

 - [The Code Explanation](#explain)
&nbsp;

 - [A fiddle demonstrating](#demo)
&nbsp;

 - [Try it yourself on this page](#tryit)
&nbsp;

 - [Use it with a bookmarklet](#bookmarklet)
&nbsp;

 - [Conclusion](#conclusion)

___

<a name="animated" />
## Animated GIFs are awesome, why would anyone not want to see a loop of my cat falling off a shelf?
While I am sure your cat GIF is hilarious, some people find animated GIFs distracting or even worse distressing.

For example people with vestibular (movement related) disorders can find themselves feeling sick due to GIF movement.

Or people with ADHD and or Autism can find animated GIFs overwhelming and distracting.

Or you have people who suffer from paranoia etc. who can be unsettled by moving images.

There are loads of other conditions that mean animations can make the web far less enjoyable with your cat GIFs etc.

I am no saint, I have an animated profile picture to catch people's attention!

So here is my way of apology to anyone who has been distracted by my profile picture or any GIFs in my posts, a hacked together "solution" to animated GIFs on dev.to.

The dev.to team / community are on with a [real fix for the animated GIF issue](https://github.com/forem/forem/issues/946), however the following code could be used temporarily while that fix takes place (with a bit of testing / adjustment) as a fix like this is massive and takes time!

<a name="code" />
## The code
For some of you just seeing the code is enough....you people are far more clever than me! But for everyone else I put a description of how it works in the next section!

``` javascript
var stopAnim = (function () {
  var priv = {};
  var pub = {};
  priv.pauseAll = false;
  if(window.matchMedia){
    var mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
     if (!mediaQuery || mediaQuery.matches) { priv.pauseAll = true }
  }

  priv.stopAnim = function (img) {
    var coverImage = function () {
      var width = img.width;
      var height = img.height;
      var canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;
      canvas.getContext('2d').drawImage(img, 0, 0, width, height);
      canvas.style.position = 'absolute';
      canvas.style.top = "0";
      canvas.style.left = "50%";
      canvas.style.transform = "translate(-50%, 0)";
      canvas.setAttribute('aria-hidden', 'true');
      canvas.setAttribute('role', 'presentation');
      var parent = img.parentNode;
      parent.style.position = "relative";
      parent.style.display = "block";
      parent.insertBefore(canvas, img);
      img.style.opacity = 0.01;
    };

    if (img.complete) {
      coverImage();
    } else {
      img.addEventListener('load', coverImage, true);
    }
  }

  pub.freezeAll = function () {
    var images = document.querySelectorAll('.crayons-article__header img, .crayons-article__main img, .crayons-avatar img');

    for (x = 0; x < images.length; x++) {
      priv.stopAnim(images[x]);
    }
  };

  if (priv.pauseAll == true) {
    pub.freezeAll();
  }

  return pub;
})();
```

<a name="explain" />
## The explanation
The above code is not overly complicated, but there may be some things that may not make sense at first glance.

### Step 1
``` javascript
var images = document.querySelectorAll('.crayons-article__header img, .crayons-article__main img, .crayons-avatar img');
```

Firstly we gather all the images on the page, which we are later going to "pause". 

We gather all images as nowadays a lot of animated GIFs you see are actually animated `webP` images to save on bandwidth / file size.

Also this is not overly stressful for the CPU so grabbing all the images is not going to impact performance too much (unless a post has 100+ images of course!).

### Step 2
The function is written so that it operates in two ways, the first is if the user has expressed that they prefer reduced motion in their browser.

``` javascript
if(window.matchMedia){
    var mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
     if (!mediaQuery || mediaQuery.matches) { priv.pauseAll = true }
  }
```

The browser exposes the `prefers-reduced-motion` media query for us to check. If it is set to `reduce` then we activate the plugin automatically on page load by setting `priv.pauseAll = true`.

The second way it can be used is by calling `stopAnim.freezeAll()` at any point.

It was set up this way so you can attach a button event to allow users to switch animations off at any time (I haven't written it in a way that you can reenable images, but that is straight forward if needed).

### Step 3
We loop through each image on the page and add event listeners for when the image is loaded. `image.complete` is a fallback for IE (yes this script works in IE9, 10 and 11! Why else would I still be using `var` everywhere? 🤣).

This is so that we can ensure there is at least one frame painted for the GIF before we do the next step. 

### Step 4
When the image has loaded this is where the magic (the "hack") happens.

1. We measure the `img`

2. We create a canvas the same size as the image

3. We grab the current frame of the GIF and paint it to the canvas

4. We add styling etc. to position the canvas (more on that in a second)

5. We add the canvas to the page and hide the image.

#### Step 4 key points / detail.
So there are a couple of key things happening here.

First, when we position the canvas we place it **behind** the current image. This is so that we don't have to do any trickery with events if they are added to an image etc.

Then we make the GIF have an opacity of 0.001 so that it is transparent (`opacity:0` used to cause issues with ChromeVox, not sure if still need to use `opacity:0.001`).

This way our new canvas shows instead, but the GIF is still exposed to screen readers (as some people use screen readers who are sighted to aid understanding!).

Along these lines that is why we also add `aria-hidden="true"` and `role="presentation"` to the generated image as we don't want it interfering with the accessibility tree.


<a name="demo" />
## A fiddle demonstrating
Press the button in the following fiddle and the animations should "pause".

{% jsfiddle https://jsfiddle.net/qdhj5zeb/3/ result js html %}


<a name="tryit" />
## Try it yourself on this page
a simple way to test this is on this page (you will have to reload if you want the animations back!)

Open developer tools (F12) and head to the "console" tab.

Then in the bottom of the window copy and paste the following code and press <kbd>Enter</kbd>. You will see that the GIFs stop animating!


``` javascript

(function(){var d={},e={};d.stopAnim=function(a){var g=function(){var c=a.width,f=a.height,b=document.createElement("canvas");b.width=c;b.height=f;b.getContext("2d").drawImage(a,0,0,c,f);b.style.position="absolute";b.style.top="0";b.style.left="50%";b.style.transform="translate(-50%, 0)";b.setAttribute("aria-hidden","true");b.setAttribute("role","presentation");c=a.parentNode;c.style.position="relative";c.style.display="block";c.insertBefore(b,a);a.style.opacity=.01};a.complete?g():a.addEventListener("load",
g,!0)};e.freezeAll=function(){var a=document.querySelectorAll(".crayons-article__header img, .crayons-article__main img, .crayons-avatar img");for(x=0;x<a.length;x++)d.stopAnim(a[x])};e.freezeAll()})();

```

here is an animated GIF image for you to test against. Also check my profile picture as that should stop animating too.

![person shaking their head side to side wearing white suit - "sassy"](https://media3.giphy.com/media/iZ0vqe8GsAADI6DXvO/200w.gif?cid=ecf05e47nlwmjl4h81m5viva2w0s0bugtpgxtkcvo8az3svj&rid=200w.gif)



After you have stopped a GIF animating try right-click -> inspect on the now stopped image to get a full understanding of what is going on!

<a name="bookmarklet" />
## Use it with a bookmarklet
If you want a super simple way to use this on the site then here it is as a bookmarklet.

Simple click and drag the link in the fiddle below to your bookmarks bar and you can then press the bookmark on any article or on the home page feed on dev.to to pause animated profile pictures and or animated post images!

{% jsfiddle https://jsfiddle.net/3k7v95wo/ result html %}



## Conclusion
Look this is not the proper way to do it, the correct way is to process the image server side to create a static image from the GIF. Then use media queries to check if `prefers-reduced-motion` has been set to `reduce` and send the static image if people prefer reduced motion.

However that can be a lot of work to implement on a larger site and sometimes a "hot fix" will hold for a few weeks while you do things properly is a viable option.

This is obviously terrible for performance and you can't just copy paste it onto any site as the selectors used are unique to dev.to (although that wouldn't take much work), but it works....and as we know that is always 50% of the battle!

So what do you think? Is it a hack that could make it to production? Is it broken for you (and if so what browser)? Does it make you feel uneasy using hacks like this?!???

Let me know in the comments!