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

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
雷峰网
雷峰网
The Register - Security
The Register - Security
The Cloudflare Blog
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
I
InfoQ
博客园 - 三生石上(FineUI控件)
H
Help Net Security
博客园 - 司徒正美
Vercel News
Vercel News
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
B
Blog
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
L
LangChain Blog
人人都是产品经理
人人都是产品经理
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recorded Future
Recorded Future
小众软件
小众软件
Martin Fowler
Martin Fowler
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Apple Machine Learning Research
Apple Machine Learning Research
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
Y
Y Combinator Blog
V
Visual Studio Blog
F
Fortinet All Blogs
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
V
V2EX
Blog — PlanetScale
Blog — PlanetScale

CSS Articles by Temani Afif

Get Ready For the Powerful CSS border-shape Property! | CSS-Tricks Let’s Play With Gap Decorations! How to Control Infinite CSS Animations (Part 2 of 2) How to Control Infinite CSS Animations (Part 1 of 2) Two Circles, One Arrow, and Anchor Positioning Making a Responsive Pyramidal Grid With Modern CSS | CSS-Tricks How to Create a CSS-only Elastic Text Effect Making Complex CSS Shapes Using shape() | CSS-Tricks Responsive Hexagon Grid Using Modern CSS | CSS-Tricks Responsive List of Avatars Using Modern CSS (Part 2) | CSS-Tricks Responsive List of Avatars Using Modern CSS (Part 1) | CSS-Tricks Perfectly Pointed Tooltips: To The Corners Perfectly Pointed Tooltips: All Four Sides Perfectly Pointed Tooltips: A Foundation Sequential linear() Animation With N Elements | CSS-Tricks Infinite Marquee Animation using Modern CSS Better CSS Shapes Using shape() — Part 4: Close and Move | CSS-Tricks Drawing CSS Shapes using corner-shape Better CSS Shapes Using shape() — Part 3: Curves | CSS-Tricks Better CSS Shapes Using shape() — Part 2: More on Arcs | CSS-Tricks Better CSS Shapes Using shape() — Part 1: Lines and Arcs | CSS-Tricks Creating Blob Shapes using clip-path: shape() Creating Flower Shapes using clip-path: shape() Custom progress element using the attr() function A CSS-Only Star Rating Component and More! (Part 2) | CSS-Tricks A CSS-Only Star Rating Component and More! (Part 1) | CSS-Tricks How to Create Wavy Boxes Using CSS Full-Bleed Layout with Modern CSS Fancy Menu Navigation Using Anchor Positioning | CSS-Tricks How to Create a Zig-Zag Box Using CSS How to Create Zig-Zag CSS Loaders Using One Element Custom Progress Element Using Anchor Positioning & Scroll-Driven Animations How to Create Filling CSS Loaders Using One Element How to Create Curved-Edge and Rounded-Edge Shapes Using CSS CSS Tricks That Use Only One Gradient | CSS-Tricks How to create Shapes with Inner Curves using CSS Mask Custom Range Slider Using Anchor Positioning & Scroll-Driven Animations How to Get the Width/Height of Any Element in Only CSS How Keyboard Navigation Works in a CSS Game How To Create Cut-Out Shapes using The clip-path property The Modern Guide For Making CSS Shapes — Smashing Magazine css-shape.com: The Ultimate Collection of CSS-only Shapes ⚡️ Sliding 3D Image Frames In CSS — Smashing Magazine CSS Tricks To Master The clip-path Property Creating Wavy Circles with Fancy Animations in CSS Modern CSS Tooltips And Speech Bubbles (Part 2) — Smashing Magazine Do you need a Tooltip or a Speech Bubble? I have created 100 using CSS 😲 Modern CSS Tooltips And Speech Bubbles (Part 1) — Smashing Magazine Creating Flower Shapes using CSS Mask & Trigonometric Functions CSS Shapes: The Triangle The Complex But Awesome CSS border-image Property — Smashing Magazine CSS Tricks to add 3D Effects to your Text CSS Responsive Multi-Line Ribbon Shapes (Part 2) — Smashing Magazine CSS Responsive Multi-Line Ribbon Shapes (Part 1) — Smashing Magazine I have made 100+ CSS-only Ribbon Shapes | The Perfect Collection 🎀 How to create a CSS-only infinite scroll animation Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 2) — Smashing Magazine Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 1) — Smashing Magazine How to Create CSS Ribbon Shapes with a Single Element CSS Shapes: The Ribbon Revealing Images With CSS Mask Animations — Smashing Magazine css-loaders.com: The Biggest Collection of Loading Animations (more than 500 🤯) How To Define An Array Of Colors With CSS — Smashing Magazine CSS effects on images II Shines, Perspective, And Rotations: Fancy CSS 3D Effects For Images — Smashing Magazine What's your Lucky CSS Pattern? Check It Now! How to Add a CSS Reveal Animation to Your Images Modern Layouts using CSS Grid I created 100+ unique CSS patterns | The best collection 🤩 How to Create a Custom Range Slider Using CSS CSS Shapes: The Heart CSS Shapes: Polygon & Starburst A Text Reveal Animation using CSS CSS Tip: learn CSS the easy way! How to build a CSS-only accordion How to create Breadcrumb Navigation with CSS Different Ways to Get CSS Gradient Shadows | CSS-Tricks A CSS-only responsive Stepper component A Fancy Hover Effect For Your Avatar | CSS-Tricks How to make a zoom effect using CSS Creating a Custom Cursor using CSS CSS Infinite 3D Sliders | CSS-Tricks How to create a responsive sidebar menu using CSS CSS Infinite and Circular Rotating Image Slider | CSS-Tricks Making Static Noise From a Weird CSS Gradient Bug | CSS-Tricks CSS Grid and Custom Shapes, Part 3 | CSS-Tricks Fancy Image Decorations: Outlines and Complex Animations | CSS-Tricks Fancy Image Decorations: Masks and Advanced Hover Effects | CSS-Tricks Fancy Image Decorations: Single Element Magic | CSS-Tricks How to create an infinite image slider using CSS How to Create Wavy Shapes & Patterns in CSS | CSS-Tricks How I Made a Pure CSS Puzzle Game | CSS-Tricks How to create a Tooltip/Speech Bubble using CSS CSS Grid and Custom Shapes, Part 2 | CSS-Tricks CSS Grid and Custom Shapes, Part 1 | CSS-Tricks Zooming Images in a Grid Layout | CSS-Tricks How to create a CSS-only loader with one element Exploring CSS Grid’s Implicit Grid and Auto-Placement Powers | CSS-Tricks How to create background pattern using CSS & conic-gradient Single Element Loaders: The Bars | CSS-Tricks
CSS Infinite Slider Flipping Through Polaroid Images | CSS-Tricks
Temani Afif · 2022-12-09 · via CSS Articles by Temani Afif

In the last article, we made a pretty cool little slider (or “carousel” if that’s what you prefer) that rotates in a circular direction. This time we are going to make one that flips through a stack of Polaroid images.

Cool right? Don’t look at the code quite yet because there’s a lot to unravel. Join me, will ya?

The basic setup

Most of the HTML and CSS for this slider is similar to the circular one we made last time. In fact, we’re using the exact same markup:

<div class="gallery">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
  <img src="" alt="">
</div>

And this is the basic CSS that sets our parent .gallery container as a grid where all the images are stacked one on top of one another:

.gallery  {
  display: grid;
  width: 220px; /* controls the size */
}
.gallery > img {
  grid-area: 1 / 1;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border: 10px solid #f2f2f2;
  box-shadow: 0 0 4px #0007;
}

Nothing complex so far. Even for the Polaroid-like style for the images, all I’m using is some border and box-shadow. You might be able to do it better, so feel free to play around with those decorative styles! We’re going to put most of our focus on the animation, which is the trickiest part.

What’s the trick?

The logic of this slider relies on the stacking order of the images — so yes, we are going to play with z-index. All of the images start with the same z-index value (2) which will logically make the last image on the top of the stack.

We take that last image and slide it to the right until it reveals the next image in the stack. Then we decrease the image’s z-index value then we slide it back into the deck. And since its z-index value is lower than the rest of the images, it becomes the last image in the stack.

Here is a stripped back demo that shows the trick. Hover the image to activate the animation:

Now, imagine the same trick applied to all the images. Here’s the pattern if we’re using the :nth-child() pseudo-selector to differentiate the images:

  • We slide the last image (N). The next image is visible (N - 1).
  • We slide the next image (N - 1). The next image is visible (N - 2)
  • We slide the next image (N - 2). The next image is visible (N - 3)
  • (We continue the same process until we reach the first image)
  • We slide the first image (1). The last image (N) is visible again.

That’s our infinite slider!

Dissecting the animation

If you remember the previous article, I defined only one animation and played with delays to control each image. We will be doing the same thing here. Let’s first try to visualize the timeline of our animation. We will start with three images, then generalize it later for any number (N) of images.

Diagramming the three parts of the animation.

Our animation is divided into three parts: “slide to right”, “slide to left” and “don’t move”. We can easily identify the delay between each image. If we consider that the first image starts at 0s, and the duration is equal to 6s, then the second one will start at -2s and the third one at -4s.

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 6s / 3 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 6s / 3 */

We can also see that the “don’t move” part takes two-thirds of the whole animation (2*100%/3) while the “slide to right” and “slide to left” parts take one-third of it together — so, each one is equal to 100%/6 of the total animation.

We can write our animation keyframes like this:

@keyframes slide {
  0%     { transform: translateX(0%); }
  16.67% { transform: translateX(120%); }
  33.34% { transform: translateX(0%); }
  100%   { transform: translateX(0%); } 
}

That 120% is an arbitrary value. I needed something bigger than 100%. The images need to slide to the right away from the rest of the images. To do that, it needs to move by at least 100% of its size. That’s why I went 120% — to gain some extra space.

Now we need to consider the z-index. Don’t forget that we need to update the image’s z-index value after it slides to the right of the pile, and before we slide it back to the bottom of the pile.

@keyframes slide {
  0%     { transform: translateX(0%);   z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  100%   { transform: translateX(0% );  z-index: 1; }  
}

Instead of defining one state at the 16.67% (100%/6) point in the timeline, we are defining two states at nearly identical points (16.66% and 16.67%) where the z-index value decreases before we slide back the image back to the deck.

Here’s what happens when we pull of all that together:

Hmmm, the sliding part seems to work fine, but the stacking order is all scrambled! The animation starts nicely since the top image is moving to the back… but the subsequent images don’t follow suit. If you notice, the second image in the sequence returns to the top of the stack before the next image blinks on top of it.

We need to closely follow the z-index changes. Initially, all the images have are z-index: 2. That means the stacking order should go…

Our eyes 👀 --> 3rd (2) | 2nd (2) | 1st (2)

We slide the third image and update its z-index to get this order:

Our eyes 👀 --> 2nd (2) | 1st (2) | 3rd (1)

We do the same with the second one:

Our eyes 👀 --> 1st (2) | 3rd (1) | 2nd (1)

…and the first one:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

We do that and everything seems to be fine. But in reality, it’s not! When the first image is moved to the back, the third image will start another iteration, meaning it returns to z-index: 2:

Our eyes 👀 --> 3rd (2) | 2nd (1) | 1st (1)

So, in reality we never had all the images at z-index: 2 at all! When the images aren’t moving (i.e., the “don’t move” part of the animation) the z-index is 1. If we slide the third image and update its z-index value from 2 to 1, it will remain on the top! When all the images have the same z-index, the last one in the source order — our third image in this case — is on top of the stack. Sliding the third image results in the following:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

The third image is still on the top and, right after it, we move the second image to the top when its animation restarts at z-index: 2:

Our eyes 👀 --> 2nd (2) | 3rd (1) | 1st (1)

Once we slide it, we get:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

Then the first image will jump on the top:

Our eyes 👀 --> 1st(2) | 3rd (1) | 2nd (1)

OK, I am lost. All the logic is wrong then?

I know, it’s confusing. But our logic is not completely wrong. We only have to rectify the animation a little to make everything work the way we want. The trick is to correctly reset the z-index.

Let’s take the situation where the third image is on the top:

Our eyes 👀 -->  3rd (2) | 2nd (1) | 1st (1)

We saw that sliding the third image and changing its z-index keeps it on top. What we need to do is update the z-index of the second image. So, before we slide the third image away from the deck, we update the z-index of the second image to 2.

In other words, we reset the z-index of the second image before the animation ends.

Diagramming the parts of the animation with indicators for where z-index is increased or decreased.

The green plus symbol represents increasing z-index to 2, and the red minus symbol correlates to z-index: 1. The second image starts with z-index: 2, then we update it to 1 when it slides away from the deck. But before the first image slides away from the deck, we change the z-index of the second image back to 2. This will make sure both images have the same z-index, but still, the third one will remain on the top because it appears later in the DOM. But after the third image slides and its z-index is updated, it moves to the bottom.

This two-thirds through the animation, so let’s update our keyframes accordingly:

@keyframes slide {
  0%     { transform: translateX(0%);   z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  66.33% { transform: translateX(0%);   z-index: 1; }
  66.34% { transform: translateX(0%);   z-index: 2; } /* and also here */
  100%   { transform: translateX(0%);   z-index: 2; }  
}

A little better, but still not quite there. There’s another issue…

Oh no, this will never end!

Don’t worry, we are not going to change the keyframes again because this issue only happens when the last image is involved. We can make a “special” keyframe animation specifically for the last image to fix things up.

When the first image is on the top, we have the following situation:

Our eyes 👀 -->  1st (2) | 3rd (1) | 2nd (1)

Considering the previous adjustment we made, the third image will jump on the top before the first image slides. It only happens in this situation because the next image that moves after the first image is the last image which has a higher order in the DOM. The rest of the images are fine because we have N, then N - 1, then we go from 3 to 2, and 2 to 1… but then we go from 1 to N.

To avoid that, we will use the following keyframes for the last image:

@keyframes slide-last {
  0%     { transform: translateX(0%);   z-index: 2;}
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  83.33% { transform: translateX(0%);   z-index: 1; }
  83.34% { transform: translateX(0%);   z-index: 2; } /* and also here */
  100%   { transform: translateX(0%);   z-index: 2; }
}

We reset the z-index value 5/6 through the animation (instead of two-thirds) which is when the first image is out of the pile. So we don’t see any jumping!

TADA! Our infinite slider is now perfect! Here’s our final code in all its glory:

.gallery > img {
  animation: slide 6s infinite;
}
.gallery > img:last-child {
  animation-name: slide-last;
}
.gallery > img:nth-child(2) { animation-delay: -2s; } 
.gallery > img:nth-child(3) { animation-delay: -4s; }

@keyframes slide {
  0% { transform: translateX(0%); z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } 
  33.34% { transform: translateX(0%); z-index: 1; }
  66.33% { transform: translateX(0%); z-index: 1; }
  66.34% { transform: translateX(0%); z-index: 2; } 
  100% { transform: translateX(0%); z-index: 2; }
}
@keyframes slide-last {
  0% { transform: translateX(0%); z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; }
  33.34% { transform: translateX(0%); z-index: 1; }
  83.33% { transform: translateX(0%); z-index: 1; }
  83.34% { transform: translateX(0%); z-index: 2; } 
  100%  { transform: translateX(0%); z-index: 2; }
}

Supporting any number of images

Now that our animation works for three images, let’s make it work for any number (N) of images. But first, we can optimize our work a little by splitting the animation up to avoid redundancy:

.gallery > img {
  z-index: 2;
  animation: 
    slide 6s infinite,
    z-order 6s infinite steps(1);
}
.gallery > img:last-child {
  animation-name: slide, z-order-last;
}
.gallery > img:nth-child(2) { animation-delay: -2s; } 
.gallery > img:nth-child(3) { animation-delay: -4s; }

@keyframes slide {
  16.67% { transform: translateX(120%); }
  33.33% { transform: translateX(0%); }
}
@keyframes z-order {
  16.67%,
  33.33% { z-index: 1; }
  66.33% { z-index: 2; }
}
@keyframes z-order-last {
  16.67%,
  33.33% { z-index: 1; }
  83.33% { z-index: 2; }
}

Way less code now! We make one animation for the sliding part and another one for the z-index updates. Note that we use steps(1) on the z-index animation. That’s because I want to abruptly change the z-index value, unlike the sliding animation where we want smooth movement.

Now that the code is easier to read and maintain, we have a better view for figuring out how to support any number of images. What we need to do is update the animation delays and the percentages of the keyframes. The delay are easy because we can use the exact same loop we made in the last article to support multiple images in the circular slider:

@for $i from 2 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    animation-delay: calc(#{(1 - $i)/$n}*6s);
  }
}

That means we’re moving from vanilla CSS to Sass. Next, we need to imagine how the timeline scale with N images. Let’s not forget that the animation happens in three phases:

Showing the three parts of the animation in a series of lines with arrows.

After “slide to right” and “slide to left”, the image should stay put until the rest of the images go through the sequence. So the “don’t move” part needs to take the same amount of time as (N - 1) as “slide to right” and “slide to left”. And within one iteration, N images will slide. So, “slide to right” and “slide to left” both take 100%/N of the total animation timeline. The image slides away from the pile at (100%/N)/2 and slides back at 100%/N .

We can change this:

@keyframes slide {
  16.67% { transform: translateX(120%); }
  33.33% { transform: translateX(0%); }
}

…to this:

@keyframes slide {
  #{50/$n}%  { transform: translateX(120%); }
  #{100/$n}% { transform: translateX(0%); }
}

If we replace N with 3, we get 16.67% and 33.33% when there are 3 images in the stack. It’s the same logic with the stacking order where we will have this:

@keyframes z-order {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  66.33% { z-index: 2; }
}

We still need to update the 66.33% point. That’s supposed to be where the image resets its z-index before the end of the animation. At that same time, the next image starts to slide. Since the sliding part takes 100%/N, the reset should happen at 100% - 100%/N:

@keyframes z-order {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  #{100 - 100/$n}% { z-index: 2; }
}

But for our z-order-last animation to work, it should happen a bit later in the sequence. Remember the fix we did for the last image? Resetting the z-index value needs to happen when the first image is out of the pile and not when it starts sliding. We can use the same reasoning here in our keyframes:

@keyframes z-order-last {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  #{100 - 50/$n}% { z-index: 2; }
}

We are done! Here’s what we get when using five images:

We can add a touch of rotation to make things a bit fancier:

All I did is append rotate(var(--r)) to the transform property. Inside the loop, --r is defined with a random angle:

@for $i from 1 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    --r: #{(-20 + random(40))*1deg}; /* a random angle between -20deg and 20deg */
  }
}

The rotation creates small glitches as we can sometimes see some of the images jumping to the back of the stack, but it’s not a big deal.

Wrapping up

All that z-index work was a big balancing act, right? If you were unsure how stacking order work before this exercise, then you probably have a much better idea now! If you found some of the explanations hard to follow, I highly recommend you to take another read of the article and map things out with pencil and paper. Try to illustrate each step of the animation using a different number of images to better understand the trick.

Last time, we used a few geometry tricks to create a circular slider that rotates back to the first image after a full sequence. This time, we accomplished a similar trick using z-index. In both cases, we didn’t duplicate any of the images to simulate a continuous animation, nor did we reach for JavaScript to help with the calculations.

Next time, we will make 3D sliders. Stay tuned!