惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
N
News and Events Feed by Topic
L
LINUX DO - 热门话题
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
爱范儿
爱范儿
D
DataBreaches.Net
Simon Willison's Weblog
Simon Willison's Weblog
S
Secure Thoughts
S
SegmentFault 最新的问题
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 叶小钗
P
Proofpoint News Feed
The Hacker News
The Hacker News
T
ThreatConnect
N
News and Events Feed by Topic
T
Threatpost
The Register - Security
The Register - Security
WordPress大学
WordPress大学
博客园 - Franky
Recorded Future
Recorded Future
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
腾讯CDC
F
Future of Privacy Forum
F
Full Disclosure
Cyberwarzone
Cyberwarzone
J
Java Code Geeks
李成银的技术随笔
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
H
Hacker News: Front Page
人人都是产品经理
人人都是产品经理
博客园_首页
Scott Helme
Scott Helme
Google DeepMind News
Google DeepMind News
美团技术团队
Malwarebytes
Malwarebytes
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
Recent Announcements
Recent Announcements
C
CXSECURITY Database RSS Feed - CXSecurity.com

CSS-Tricks

Revealing Text With CSS letter-spacing | CSS-Tricks Technical Writing in the AI Age | CSS-Tricks Cross-Document View Transitions: Scaling Across Hundreds of Elements | CSS-Tricks Cross-Document View Transitions: Scaling Across Hundreds of Elements | CSS-Tricks The State of CSS Centering in 2026 | CSS-Tricks Stack Overflow: When We Stop Asking | CSS-Tricks Cross-Document View Transitions: The Gotchas Nobody Mentions | CSS-Tricks What’s !important #11: 3D Voxel Scenes, Flying Focus, CSS Syntaxes, and More | CSS-Tricks Computing and Displaying Discounted Prices in CSS | CSS-Tricks rotateX() | CSS-Tricks rotateY() | CSS-Tricks rotateZ() | CSS-Tricks rotate() | CSS-Tricks Soon We Can Finally Banish JavaScript to the ShadowRealm | CSS-Tricks Using CSS corner-shape For Folded Corners | CSS-Tricks A Scrollytelling Gift for Mum on Mother’s Day 2026 | CSS-Tricks Google’s Prompt API | CSS-Tricks Making Zigzag CSS Layouts With a Grid + Transform Trick | CSS-Tricks Fixed-Height Cards: More Fragile Than They Look | CSS-Tricks What’s !important #10: HTML-in-Canvas, Hex Maps, E-ink Optimization, and More | CSS-Tricks The Importance of Native Randomness in CSS | CSS-Tricks contrast() | CSS-Tricks contrast-color() | CSS-Tricks Let’s Use the Nonexistent ::nth-letter Selector Now | CSS-Tricks Quick Hit #126 Recreating Apple’s Vision Pro Animation in CSS | CSS-Tricks Quick Hit #125 Enhancing Astro With a Markdown Component | CSS-Tricks Quick Hit #124 Markdown + Astro = ❤️ | CSS-Tricks Quick Hit #123 What’s !important #9: clip-path Jigsaws, View Transitions Toolkit, Name-only Containers, and More | CSS-Tricks A Well-Designed JavaScript Module System is Your First Architecture Decision | CSS-Tricks hypot() | CSS-Tricks The Radio State Machine | CSS-Tricks 7 View Transitions Recipes to Try | CSS-Tricks Quick Hit #122 Quick Hit #121 Selecting a Date Range in CSS | CSS-Tricks saturate() | CSS-Tricks justify-self | CSS-Tricks Quick Hit #120 Alternatives to the !important Keyword | CSS-Tricks Quick Hit #119 New CSS Multi-Column Layout Features in Chrome | CSS-Tricks Quick Hit #118 Making Complex CSS Shapes Using shape() | CSS-Tricks Quick Hit #117 Front-End Fools: Top 10 April Fools’ UI Pranks of All Time | CSS-Tricks Sniffing Out the CSS Olfactive API | CSS-Tricks What’s !important #8: Light/Dark Favicons, @mixin, object-view-box, and More | CSS-Tricks Quick Hit #116 Form Automation Tips for Happier User and Clients | CSS-Tricks Quick Hit #115 Generative UI Notes | CSS-Tricks Quick Hit #114 Quick Hit #113 Experimenting With Scroll-Driven corner-shape Animations | CSS-Tricks Quick Hit #112 JavaScript for Everyone: Destructuring | CSS-Tricks Quick Hit #111 Quick Hit #110 What’s !important #7: random(), Folded Corners, Anchored Container Queries, and More | CSS-Tricks 4 Reasons That Make Tailwind Great for Building Layouts | CSS-Tricks Quick Hit #109 Quick Hit #108 Abusing Customizable Selects | CSS-Tricks Quick Hit #107 The Value of z-index | CSS-Tricks Quick Hit #106 The Different Ways to Select <html> in CSS Quick Hit #105 Popover API or Dialog API: Which to Choose? Quick Hit #104 What’s !important #6: :heading, border-shape, Truncating Text From the Middle, and More Yet Another Way to Center an (Absolute) Element An Exploit ... in CSS?! Quick Hit #103 A Complete Guide to Bookmarklets Quick Hit #102 Loading Smarter: SVG vs. Raster Loaders in Modern Web Design Potentially Coming to a Browser :near() You Quick Hit #101 Distinguishing "Components" and "Utilities" in Tailwind Quick Hit #100 Spiral Scrollytelling in CSS With sibling-index() Interop 2026 Quick Hit #99 What’s !important #5: Lazy-loading iframes, Repeating corner-shape Backgrounds, and More Quick Hit #98 Making a Responsive Pyramidal Grid With Modern CSS Approximating contrast-color() With Other CSS Features Quick Hit #97 Trying to Make the Perfect Pie Chart in CSS Quick Hit #96 Quick Hit #95 CSS Bar Charts Using Modern Functions Quick Hit #94 No Hassle Visual Code Theming: Publishing an Extension Quick Hit #93
Repeatable, Staggered Animation Three Ways: Sass, GSAP and Web Animations API
CSS-Tricks · 2017-07-04 · via CSS-Tricks

Staggered animation, also known as “follow through” or “overlapping action” is one of the twelve Disney principles of animation as defined by Ollie Johnston and Frank Thomas in their 1981 book “The Illusion of Life”. At its core, the concept deals with animating objects in delayed succession to produce fluid motion.

The technique doesn’t only apply to cute character animations though. The Motion design aspect of a digital interface has significant implications on UX, user perception and “feel”. Google even makes a point to mention staggered animation in its Motion Choreography page, as part of the Material Design guide:

While the topic of motion design is truly vast, I often find myself applying bits and pieces even in smallest of projects. During the design process of the Interactive Coke ad on Eko I was tasked with creating some animation to be shown as the interactive video is loading, and so this mockup was born:

At a first glance, this animation seems trivial to implement in CSS, but turns out that is not that case! While it might be simpler with GSAP and the shiny new Web Animations API, doing so with CSS requires a few tricks which I’m going to explain in this post. Why use CSS at all then? In this case — as the animation was meant to run while the user waits for assets to load, it didn’t make much sense to load an animation library just to display a loading spinner.

First, a bit about the anatomy of the animation.

There are four circles, absolutely positioned within a container with overflow: hidden to frame and crop the edges of the two outermost circles. Why four and not three? Because the first one is offscreen, waiting to enter stage left and the last one exists the frame stage right. The other two are always in the frame. This way, the end state of the animation iteration looks exactly like its beginning state. Circle 1 takes circle 2’s place, circle 2 takes circle 3’s place and so on.

Here’s the basic HTML:

<div id="container">
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</div>

And the accompanying CSS:

#container {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 160px;
  height: 40px;
  display: block;
  overflow: hidden;
}
span {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #4df5c4;
  display: inline-block;
  position: absolute; 
  transform: translateX(0px);
}

Let’s try this out with a simple animation for each circle that translates X from 0 to 60 pixels:

See the Pen dot loader – no stagger by Opher Vishnia (@OpherV) on CodePen.

Looks kind of weird and robotic, right? That’s because we’re missing one major component: Staggered animation. That is, each circle’s animation needs to start a bit after its predecessor. “No problem!”, you might think to yourself, “let’s use the animation-delay” property. “We’ll give the 4th circle a value of 0s, the 3rd of 0.15s and so on”. Alright, let’s try that:

See the Pen dot loader – broken by Opher Vishnia (@OpherV) on CodePen.

Hmm… What just happened? The property animation-delay affects only the initial delay before the animations starts. It doesn’t add additional delays between every iteration so the animation goes out of sync like in the following diagram:

Math to the rescue

To overcome this, I baked the delay into the animation. CSS keyframe animations are specified in percents, and with some calculation, you can use those to define how much delay should the animation include. For example, if you set an animation-duration of 1s, and specify your start keyframe at 0%, the same values at 20%, your end at 80% and the same end values at 100%, your animation will wait 0.2 seconds, run for 0.6 seconds, then wait for another 0.2 seconds.

In my case, I wanted each circle to wait with a stagger time of 0.15 seconds before performing the actual animation taking 0.5 seconds, with the entire process taking 1 second. This means that the 4th circle animation waits 0 seconds, then animates for 0.5 seconds and waits for another 0.5 seconds. The second circle waits 0.15 seconds, then animates 0.5 seconds and waits for 0.35 seconds and so forth.

To achieve this, you need four keyframes (or three keyframe pairs): 1 and 2 account for the stagger wait, 2 and 3 for the actual animation time while 3 and 4 account for the final wait. The “trick” is to understand how to convert the required timings into keyframe percentages, but that’s a relatively simple calculation. For example, the 2nd circle needs to wait 0.15 * 2 = 0.3 seconds, then animate for 0.5 seconds. I know the total time for the animation is one second, so the keyframe percentages are calculated like so:

0s = 0%
0.3s = 0.3 / 1s * 100 =  30%
0.8s = (0.3 + 0.5) / 1s * 100 = 80%
1s = 100%

The end result looks something like this:

With the entire animation, including stagger time and wait baked into the CSS keyframes taking exactly one second, the animation doesn’t go out of sync.

Luckily, Sass allows us automate this process with a simple for loop and some inline math, which ultimately compiles into a series of keyframe animations. This way you can manipulate the timing variables to experiment and test whatever works best for your animation:

@mixin createCircleAnimation($i, $animTime, $totalTime, $delay) {      
  @include keyframes(circle#{$i}) {
    0% {              
      @include transform(translateX(0));            
    }
    #{($i * $delay)/$totalTime * 100}% {     
      @include transform(translateX(0));            
    }          
    #{($i * $delay + $animTime)/$totalTime * 100}% {     
      @include transform(translateX(60px));            
    }          
    100% {
      @include transform(translateX(60px));             
    }
  }      
}

$animTime: 0.5s;
$totalTime: 1s;
$staggerTime: 0.15s;

@for $i from 0 through 3 {
  @include createCircleAnimation($i, $animTime, $totalTime, $staggerTime); 
  span:nth-child(#{($i + 1)}) {
    animation: circle#{(3 - $i)} $totalTime infinite;
    left: #{$i * 60 - 60 }px;
  }
}

And voila — here’s the final result

<

p data-height=”450 data-theme-id=” 1″=”” data-slug-hash=”bEydYo” data-default-tab=”result” data-user=”OpherV” data-embed-version=”2″ data-pen-title=”dot loading animation – SASS stagger” class=”codepen”>See the Pen dot loading animation – SASS stagger by Opher Vishnia (@OpherV) on CodePen.

There are two main caveats with this method:

First, you need to make sure the defined stagger time/animation time isn’t too long that it overlaps the total animation time, otherwise the math (and the animation) will break.

Second, this method does generate some hefty amount of CSS code, especially if you’re using Sass to emit all the prefixes for browser compatibility. In my example, I had only four items to animate, but if yours has more items, the amount of code generated might not be worth the effort, and you probably want to stick with JS based animation libraries such as GSAP. Still, doing this entirely in CSS is pretty cool.

Making life easier

To contrast the verbosity of the Sass solution, I’d like to show you how the same can be easily achieved with the use of GSAP’s Timeline, and staggerTo function:

See the Pen dot loading animation – GSAP by Opher Vishnia (@OpherV) on CodePen.

There are two interesting bits here. First, the last parameter of staggerTo, which defines the wait time between animating elements is set to a negative value (-0.15). This allows the elements to stagger in reverse order (circle 4–3–2–1 instead of 1–2–3–4). Cool, huh?

Second, see the bit with tl.set({}, {}, "1");? What’s this weird syntax all about? That’s a neat hack to implement the wait time at the end each circle’s animation. Essentially by setting an empty object to an empty object at time 1, the Timeline animation will now repeat after the 1-second mark, rather than after the circle animation had ended.

Looking forwards to the future

The Web Animations API is the new and exciting kid on the block, but out of scope for this article. I couldn’t resist providing you with a sample implementation though, which uses the same math as the CSS implementation:

See the Pen dot loading animation – WAAPI by Opher Vishnia (@OpherV) on CodePen.

Was this helpful? Have you created some smooth animations using this technique? Let me know!