Slow Running Animations in Safari

I came across this problem whilst working on this CSS-only continuous ticker slider. It stumped me for a while and I couldn’t find any good information online, so I hope this is helpful.

The Problem

Checkout the animations below:

You can see the Safari version (on the right) runs much slower than specified in the CSS. On desktop, I found this occurs the 2nd or 3rd time the page is loaded after clearing the cache or making a change to the CSS animation property. On mobile it normally happened on the first page load.

The problem was tricky as it did not occur consistently. Some hunting around on the web gave me some clues that this was an optimisation, Safari is throttling animations which it determines as not important. The criteria are not clear, but it appears some sort of interaction with the element will decrease the risk of this happening.

The fact this happened more often on mobile reinforced this theory, I would expect any optimisations to be stricter on mobile devices which generally have less resources than laptops or desktops.

The Solution

After some trial and error, I found that activating the animation when the element scrolls into view fixed the problem. This makes some sense, as it’s more efficient to only animate an element when required and not as soon as the page loads (assuming the browser doesn’t optimise for this automatically). Some JavaScript is required for this:

import 'intersection-observer' // Polyfill for older browsers which haven't implemented the IntersectionObserver API.

/**
 * Function that triggers the image strip animation on scroll.
 */
export default function scroll () {
  const animTriggerClass = 'anim-image-strip-trigger'
  const animElems = document.getElementsByClassName( 'js-image-strip' )

  const io = new IntersectionObserver(
    entries => {
      entries.forEach( entry => {
        if ( entry.intersectionRatio > 0 ) {
          entry.target.classList.add( animTriggerClass ) 
          io.unobserve( entry.target )
        }
      })
      
    }
  )

  animElems.forEach( element => {
    io.observe( element )
  })
}

Then update my CSS to trigger the animation when the anim-image-strip-trigger class is added:

.anim-image-strip-trigger {
    animation: ticker-kf 18s linear infinite;
}

In Summary

The problem has a relatively simple solution, the hard part was figuring out what the root cause was. It would be nice if Apple documented this along with guidelines for how animations should be used in Safari for optimal performance. It would also have been really helpful if they output a warning in the console instead of leaving engineers guessing!

Let’s launch something!