Options for Pausing the Animation of GIFs

A shiny yellow-green pause button with rounded corners.

GIFs used to be just atmospheric decoration.  Now they’re usually short, looping, silent videos.  As videos, it’s not very nice to have them play automatically.  It’s much better when they have a play button and only start playing when that’s clicked or tapped.

So I recently went down a rabbit hole of ways to make gifs pause by default and only play when interacted with.  This post is intended to document some of the things I found.

So, what did I find?

How to Pause a GIF

Strictly speaking, you can’t.

Let me explain what I mean by this.  As far as I can tell, there’s no programmatic way to pause a GIF and have it still be the same GIF file in an ‘img’ tag in the browser.  You can hide it and replace it with a static image.  Or you can replace it with some other kind of animating element (such as a canvas, or CSS animation).  But there are libraries or code snippets available for doing these things.  Here are a few of them.

1. The Thing That Works Right But is Not a GIF

One of the first things I stumbled across was this CodePen.  This works right from a user point of view, in that it allows you to pause the animation, and resume from where you left off, whereas most of the other methods let you stop it, but only restart animating from the beginning.  However, as the section heading says, it’s not an animated GIF.  The image (a PNG file) contains a series of frames side by side, and the CodePen uses CSS animations to switch from frame to frame.  You can pause the animation by setting the animation-play-state property to ‘paused’, but, alas, this property doesn’t work on GIFs.

animation frames side by side

2. The One With The Simplest Code

CSS PLAY has a demo that accomplishes play/stop in a small amount of code (about 8 lines of CSS, which would probably be about 20 with more line breaks for readability), and doesn’t do DOM manipulation, relying on the :active and :hover pseudo-classes to show or hide the animated GIF by setting the visibility to hidden or visible.  An element (in this case an ‘a’ tag) that encloses the ‘img’ tag has its background image set to the static image that’s displayed while the GIF isn’t animating.

One downside is that this method requires providing a separate static image for the stopped state (the next two methods are libraries that generate the static image for you by programmatically taking a screen shot of the gif).

The code for CSS PLAY’s implementation of this concept is copyrighted and not released under an open source license.  The code requires a donation for use and special permission for commercial use.  See the Copyright section of its demo page for details.

3. Gifffer

Gifffer (three f’s) is a JavaScript library with a small minified footprint.  It requires using a custom attribute instead of ‘src’ on the ‘img’ tags and then calling the Gifffer() function in your window.onload().  It works by DOM manipulation, programmatically replacing the ‘img’ element with a div inside a button, and then putting the img back when you want it to animate.

I assume that you would have to call Gifffer() again if you add additional GIFs to the page after it’s loaded.

4. Freezeframe.js

Freezeframe.js works similarly to Gifffer but is more full featured.  It appears to have a fade effect when transitioning between the animating and stopped states, which looks nice.   It doesn’t require jQuery but is available as a jQuery plugin.

The DOM manipulation for Freezeframe.js and Gifffer might conflict with the DOM manipulation done by React or similar frameworks.

5. react-gif-player

Speaking of React, react-gif-player is a GIF player for React.  It encloses the img element within divs and changes the img’s src attribute when clicked.  You do have to provide a separate still image file for it to switch to when paused.

I didn’t find any GIF player libraries for Angular or Vue.

6. gif-player

One that does allow pausing at a specific frame is gif-player (no relation to react-gif-player).   It uses the WebComponent API to provide a custom ‘gif-player’ element, which means it should be compatible with React or other modern frameworks.  The user experience isn’t as simple as clicking on a play button though.  It starts in a default state (you can either set it to autoplay or be paused by default), and then pauses when you mouse over it or touch it, and fast forwards/rewinds by moving the mouse/finger left and right across the image.

It achieves this by processing the GIF using the omggif.js library in the browser and then rendering each frame to a ‘canvas’ element.  While this does replace the img tag, it demonstrates that it’s possible to have a pausable client-side GIF player without any server-side shennanigans.

7. A note on the <video> tag

HTML5 has a ‘video’ tag that allows playback and pausing. However, GIFs (which would be a media type of “image/gif” in the ‘source’ tag)  aren’t supported.  A tutorial on HTML5 video says to convert them from GIF to a video format using ffmpeg, which can be run from the command line.  This might be a good approach if you can pre-process all your GIFs or run a background process on the server.

Which one wins?

From trying the demos of these libraries, Freezeframe.js seemed to be the best experience from the user’s point of view.   This will probably be your best bet unless you’re using React, in which case react-gif-player was pretty smooth.

This post was adapted from this twitter thread.  If you liked this, consider following me over there.  Have you made GIFs pausable for the user?  What approach worked for you?

Tech of the Week: CSS

Welcome to the first installment of a weekly(ish) segment where I learn some things about a technology and then write some things about it. This week, I spent some time looking at CSS, and I’ll share some tricks and links with you.

CSS, or Cascading Stylesheets, is a language for telling the web browser how to lay out elements on a web page, and what they should look like.  This post isn’t intended as an introduction to CSS.  If you’re looking for that, Ali Spittel recently wrote a good post about it on the DEV forum.  If you’re just looking for information on layout, skip to the Resources section, where there are links to a tutorial on layout, as well as tutorials specifically on Flexbox and Grid.

Starting Off

The fist task I set myself was to just make a nice looking but not very complicated page.  The result looked like this:

A web page with rounded corners and drop shadows

The bottom half of a web page with rounded corners and drop shadows 

A few techniques that were used on this page are: rounded corners, drop shadows, and small caps text.  I’ll give more details on those in the next sections.

Rounded Corners

Rounded corners are accomplished with the border-radius CSS rule.  In this case, it was:

border-radius: 20px;

This property can be set for each corner individually.  You can see a few elements in Twitter’s UI that use this property: 

A screenshot of Twitter's web interface, showing its use of elements with rounded corners

  • The media preview in the tweet
  • The “What’s happening?” text field where you type tweets
  • The rounded left and right sides on the Tweet button
  • The circular user icons

If you’re using Firefox you can find out how rounded corners or some other effect is achieved by right-clicking the button or other rounded-cornered element, and clicking “Inspect Element”.  (Other browsers have similar capabilities.)  If you do that for a user icon in Twitter, you’ll see something like the following:

.avatar {
width: 48px;
height: 48px;
border-radius: 50%;

Note that sometimes percent is used and sometimes px.  A border-radius of 50% makes the element circular.

This MDN page has more info on border-radius.

Drop Shadows

Drop shadows are made using something like:

filter: drop-shadow(3px 3px 3px black);

I’m not sure why it’s a “filter”, but here are the MDN pages for the filter property and the drop-shadow filter value.  There’s also a property called box-shadow, which might just be the same thing without having to use it as a filter.

Small Caps and Uppercase

A couple things you can do with text using CSS are to uppercase it, using the text-transform property:

h1, h2 {
font-family: monospace;
text-transform: uppercase;

…or to make it into small caps using the font-variant property:

a {
text-decoration: none;
font-variant: small-caps;

There’s a tutorial on text styling linked from the Resources section.


CSS is good for styling, but what about layout?  CSS has a couple relatively new features called Flexbox and Grid, which might finally allow it to catch up with the ease of use of using HTML tables for layout.  Tables are supposed to be bad for accessibility reasons, because screen readers interpret them as “tabular data” (i.e. the stuff you’d put in a spreadsheet), and so you shouldn’t use them for layout (I *guess*).  This could have been solved easily if the HTML standard had added a single property telling screen readers to just treat it as a layout table instead of a data table.  Something like:

<table semantic="false">
  <!-- and so forth -->

<!-- the semantic="false" property
doesn't actually work because
they don't want you to be happy -->

But I digress.  Flexbox and Grid exist now.  I didn’t delve into them this week.  Which is probably why things got so frustrating after I started focusing on layout without them.

One thing I spent a lot of time trying to replicate was Facebook’s layout, which has a navbar on the top and another one on the side that stay in place as you scroll. 

The top navbar that stays in place is accomplished using position: fixed.

header {
position: fixed;

/* position: fixed has
to be used with one of:
top, bottom, left, right */
top: 0;

width: 100%;
margin: 0;
padding: 1rem;
height: 1rem;

But this will obscure some content.  So the solution is to make a div that’s the same size, *without* position: fixed, to go above the content.

.fakeHeader {
height: 1rem;
margin-top: 0;
padding: 1rem;

Adding a sidebar was more difficult, and I never got it right without it screwing up the content area in some way or another.  there are recipes for adding sidebars (one of which is linked in the Resources section), but they tend to assume the lack of a top navbar.  Whenever I got the sidebar looking right, the content area would always get messed up in some way.  I’m guessing the solution would be to just use Flexbox to define the horizontal areas of the hidden fake topnav and the area below it, and then use another Flexbox to partition the area below the topnav into sidebar and content areas. 

Resources and Further Reading

Here are some links that I came across in my studies.


That’s it for this week.  Next week(ish) I’m going to be looking at the inner workings of WordPress themes, which should let me apply some of this CSS knowledge to give this blog a custom look.   If you want to be notified of future posts, you can subscribe to the Tech of the Week tag via RSS, subscribe to my whole blog via RSS, or follow me on Twitter.  The latter two might contain stuff that’s not tech-related.  In fact, the Twitter one definitely does.  Or you can just sit and refresh the blog for a week (probably more like two, tbh) until a new post appears.  See you then.

Update: The weekly schedule is no longer a thing, and I haven’t, as of 10/12/2021, done a post on WordPress themes. See here for details.

WordPress is More Complicated Than I Remember

I’m finally setting my blog back up, after some issues with my old web host.  My not wanting to bother with those issues has caused it to be down for a while.  I’m using WordPress again, this time with Bluehost (which I’ve been mistakenly calling Blue Origin, which is a rocket company instead of a web host).

Bluehost was recommended as a host that wouldn’t require one to set things up oneself at the command line, and the process of setting up has been fairly painless.  I had a question about backups, which their onboarding support was able to answer (backups are part of the “prime” tier, but can be added to the lower tiers).  Blue host conveniently lets you chat on the website with a support person.  The backup tool is called CodeGuard.  I was about to say, “Note to self: set up backups”, but they’ve apparently already been running.

One confusing issue I ran into was with pointing my existing domain at the new host.  (This is something support could have helped me with, if I’d gone back to the chat, but I was stubborn.)  The default seemed to be to either register a new domain, or transfer registration of an existing domain to Bluehost.  I’m happy with the domain being registered at Namecheap, and it wasn’t immediately clear what I should choose for that.  It ended up being something like “add domain in cPanel”.  cPanel, I have learned, is some software for managing shared hosting.  Perhaps it’s the core around which something like Bluehost is built.

If I were registering the domain through Bluehost, I probably wouldn’t have had to think about cPanel though.

As far as WordPress itself, there seem to be a lot more settings one has to fiddle with, in order to make a site that’s (relatively) aesthetically pleasing.  Switching to one of the default themes helped though.  I initially tried OnePress, because, at least in one configuration, had a nice minimalist feel.  But when I couldn’t get it to stop advertising for itself on my home page, I gave up and went with the built in Twenty-Seventeen theme.  A bit of fiddling later, and I’m mostly happy with the site, though I think the header image is too big.  A lot of the clutter of settings goes away, though, when you add and log in as a non-admin user.

The reason I’m thinking and writing about this is because my dad had asked me for advice about making a website.  Short of coding everything from scratch, the main contenders were WordPress (plus some shared hosting provider) and Wix.  So I’m thinking about the usability of this process, not just for me, but for someone not familiar with web development.

So I wonder how obvious the seams are between the various pieces of software to someone not trained in the LAMP stack: Bluehost’s (or some other shared host’s) proprietary interface for managing the overall site or sites; one or more instances of WordPress; cPanel; the various other tools like phpMyAdmin under Bluehost’s “Advanced” tab.

I’m still waiting for the domain name itself to realize it’s not at my old host.  Domain changes are expected to take a while to propagate, though I don’t know why it should take so long.  (Chrome sees the right host though, hm..)  Once that takes effect, I will once again have a website, like a proper citizen of the web.