DEV Community

Cover image for Pausing GIF animations on dev.to for those who `prefer-reduced-motion` [hack 1]
GrahamTheDev
GrahamTheDev

Posted on • Edited on

Pausing GIF animations on dev.to for those who `prefer-reduced-motion` [hack 1]

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 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?

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, 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!

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!

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;
})();
Enter fullscreen mode Exit fullscreen mode

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

var images = document.querySelectorAll('.crayons-article__header img, .crayons-article__main img, .crayons-avatar img');
Enter fullscreen mode Exit fullscreen mode

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.

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

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 fiddle demonstrating

Press the button in the following fiddle and the animations should "pause".

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 Enter. You will see that the GIFs stop animating!


(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()})();

Enter fullscreen mode Exit fullscreen mode

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 -

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!

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!

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!

Top comments (5)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
link2twenty profile image
Andrew Bone

SVGs have a built in function called pauseAnimations, and also unpauseAnimations, which allows you to stop an animation in its tracks.

SVGSVGElement.pauseAnimations()
Suspends (i.e., pauses) all currently running animations that are defined within the SVG document fragment corresponding to this <svg> element, causing the animation clock corresponding to this document fragment to stand still until it is unpaused.

developer.mozilla.org/en-US/docs/W...

Here's a very basic demo

Collapse
 
grahamthedev profile image
GrahamTheDev

Well that solves SMIL and I was today years old when I learned about SVG pauseAnimations!

I was thinking about it and would the following not work for CSS applied animations in 99% of circumstances (I know a more specific selector with !important would obviously slip through but other than that I think it would work?)?

svg *{
    transition-property: none !important;
    transition-duration: 0s !important;
    animation: none !important;
    animation-duration: 0s !important;
    animation-iteration-count: 0 !important;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
grahamthedev profile image
GrahamTheDev

So for dev.to this isn't an issue (we obviously can't stop what people do in fiddles in any circumstance, but you can't link to or upload SVG files on dev.to so it will never be a problem here - unless you can and I missed something!).

For any site that does accept SVG but doesn't have control of the images it might be difficult. You would have to probably use computed style on every path, group etc. within the SVG and remove any animation CSS.

Then you would have to find any SMIL animations and remove those also.

I would imagine we would instead just block the image to be honest and link to it if SVGs were accepted on a site and someone had a preference for prefers-reduced-motion.

Or maybe a better solution is to provide a toggle to hide / unhide the SVG?

Obviously if you control the SVG it is easy as you can just use media queries and put all your animation CSS there or swap an SVG using SMIL out for a static version at a push.

I am trying to work out if a similar technique to the one I used here could be used for SVG but I am pretty sure it would clone the animation into the canvas, one to experiment with if I get time!

Collapse
 
grahamthedev profile image
GrahamTheDev

One thought is save SVG to canvas, then save canvas to image? No idea if that would work but probably a path I would look at if this became a requirement.