Home >Web Front-end >CSS Tutorial >CSS Infinite Slider Flipping Through Polaroid Images
In the previous post, we made a very cool little slider (or, if you prefer, call it a "carousel") that rotates in a circular direction. This time, we will make a slider that can flip through the Polaroid image stack.
It's cool, right? Don't look at the code first, because there are many things to explain. Join me?
Most of the HTML and CSS of this slider are similar to the circular slider we made last time. In fact, we are using the exact same marker:
<code><div> <img alt="" src=""><img alt="" src=""><img alt="" src=""><img alt="" src=""> </div></code>
This is the basic CSS, which sets our parent .gallery
containers as grids, and all images are stacked on top of each other:
<code>.gallery { display: grid; width: 220px; /* 控制大小 */ } .gallery > img { grid-area: 1 / 1; width: 100%; aspect-ratio: 1; object-fit: cover; border: 10px solid #f2f2f2; box-shadow: 0 0 4px #0007; }</code>
So far, there is nothing complicated. Even for Polaroid-style images, I'm using only borders and shadows. You may be able to do better, so feel free to try these decorative styles! We're going to put most of our energy into the animation, which is the trickiest part.
The logic of this slider depends on the stacking order of the images - yes, we will use z-index
. All images start with the same z-index
value (2), which will make the last image in the stack sit at the top.
We take the last image and slide it to the right until the next image in the stack is displayed. We then lower the z-index
value of the image and slide it back to the deck. Since its z-index
value is lower than the rest of the images, it becomes the last image in the stack.
This is a simplified demonstration that shows this trick. Hover over the image to activate the animation:
Now, imagine the same trick applied to all images. If we use the :nth-child()
pseudo-selector to distinguish images, then this is the pattern:
This is our infinite slider!
If you remember the previous post, I only defined one animation and used delay to control each image. We will do the same here. Let's first try visualizing the timeline of the animation. We will start with three images and generalize them to any number (N) images.
Our animation is divided into three parts: "Swipe right", "Swipe left" and "Don't move". We can easily identify the delay between each image. If we think the first image starts at 0s and the duration is equal to 6s, then the second image will start at -2s and the third image will start at -4s.
<code><div> <img alt="" src=""><img alt="" src=""><img alt="" src=""><img alt="" src=""> </div></code>
We can also see that the “move” part takes up two-thirds of the entire animation (2*100%/3), while the “swipe right” and “swipe left” part together take up one-third – so each part equals 100%/6 of the total animation.
We can write animation keyframes like this:
<code>.gallery { display: grid; width: 220px; /* 控制大小 */ } .gallery > img { grid-area: 1 / 1; width: 100%; aspect-ratio: 1; object-fit: cover; border: 10px solid #f2f2f2; box-shadow: 0 0 4px #0007; }</code>
120% is an arbitrary value. I need a value greater than 100%. The image needs to slide to the right away from the rest of the images. To do this, it needs to move at least 100% of its size. That’s why I chose 120% – to get some extra space.
Now we need to consider z-index
. Don't forget, we need to update the image's after the image is slided out of the stack and before sliding back to the bottom of the stack.
Instead of defining a state at 16.67% (100%/6) points on the timeline, we define two states at nearly the same points (16.66% and 16.67%) where the z-index
value is lowered before we slide the image back to the deck.
<code>.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 6s / 3 */ .gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 6s / 3 */</code>This is what happens when we put all of this together:
z-index
Well, the sliding part seems to work fine, but the stacking order is all messed up! The animation starts well because the image at the top is moving to the back...but the subsequent image doesn't keep up. If you notice, the second image in the sequence returns to the top of the stack before the next image flashes to its top.
We need to carefully track the changes in
. Initially, all images have: 2. This means that the stacking order should be...
z-index
We slide the third image and update it z-index
to:
<code>@keyframes slide { 0% { transform: translateX(0%); } 16.67% { transform: translateX(120%); } 33.34% { transform: translateX(0%); } 100% { transform: translateX(0%); } }</code>
We do the same with the second image: z-index
<code>@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; } /* 我们在这里更新 z-order */ 33.34% { transform: translateX(0%); z-index: 1; } 100% { transform: translateX(0% ); z-index: 1; } }</code>…and the first image:
<code>我们的眼睛 ? --> 第三张 (2) | 第二张 (2) | 第一张 (2)</code>We did this and everything seemed to be fine. But in fact, this is not the case! When the first image moves behind, the third image starts another iteration, which means it returns to
:2:
<code>我们的眼睛 ? --> 第二张 (2) | 第一张 (2) | 第三张 (1)</code>
So, we actually don't have all the images at all:2! z-index
is 1 when the image is not moved (i.e. the "mobile" part of the animation). If we slide the third image and update its
<code>我们的眼睛 ? --> 第一张 (2) | 第三张 (1) | 第二张 (1)</code>, the last image in the source order—in this case our third image—is located at the top of the stack. Sliding the third image will result in the following results:
z-index
z-index
The third image is still on the top, and after that, when its animation restarts with z-index
:2, we move the second image to the top: z-index
<code><div> <img alt="" src=""><img alt="" src=""><img alt="" src=""><img alt="" src=""> </div></code>
Once we slide it, we get:
<code>.gallery { display: grid; width: 220px; /* 控制大小 */ } .gallery > img { grid-area: 1 / 1; width: 100%; aspect-ratio: 1; object-fit: cover; border: 10px solid #f2f2f2; box-shadow: 0 0 4px #0007; }</code>
The first image will then jump to the top:
<code>.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 6s / 3 */ .gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 6s / 3 */</code>
I know, it's confusing. But our logic is not completely wrong. We just need to correct the animation a little bit to make everything work the way we want it to. The trick is to reset z-index
correctly.
Let's consider the case where the third image is on top:
<code>@keyframes slide { 0% { transform: translateX(0%); } 16.67% { transform: translateX(120%); } 33.34% { transform: translateX(0%); } 100% { transform: translateX(0%); } }</code>
We see sliding the third image and changing it z-index
will keep it on top. What we need to do is update the second image's z-index
. So, before we slide the third image away from the deck, we update the second image z-index
to 2.
In other words, we reset the z-index
of the second image before the animation ends.
Green plus sign means increasing z-index
to 2, and red minus sign is related to z-index
:1. The second image starts with z-index
:2 and then when it slides out of the deck, we update it to 1. But before the first image slides out of the deck, we change the z-index
of the second image back to 2. This will ensure that both images have the same z-index
, however, the third image will still be on the top because it appears later in the DOM. But after the third image slides and updates it z-index
, it moves to the bottom.
This is two-thirds of the animation, so let's update the keyframe accordingly:
<code>@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; } /* 我们在这里更新 z-order */ 33.34% { transform: translateX(0%); z-index: 1; } 100% { transform: translateX(0% ); z-index: 1; } }</code>
It's a little better, but still not good enough. There is another question...
Don't worry, we won't change the keyframe again, as this problem only happens when the last image is involved. We can make a "special" keyframe animation for the last image to fix the issue.
When the first image is on top, we have the following situation:
<code>我们的眼睛 ? --> 第三张 (2) | 第二张 (2) | 第一张 (2)</code>
Considering the adjustments we made before the first image will jump to the top. This only happens in this case because the next image that moves after the first image is the higher order last image in the DOM. The rest of the images are good because we have N, then N - 1, then we go from 3 to 2, from 2 to 1… but then we go from 1 to N.
To avoid this, we will use the following keyframe for the last image:
<code>我们的眼睛 ? --> 第二张 (2) | 第一张 (2) | 第三张 (1)</code>
We reset the z-index
value when the animation goes to 5/6 (rather than two-thirds), and the first image has been moved out of the stack. So we can't see any jumps!
Success! Our Infinity Slider is now perfect! This is all about our final code:
<code>我们的眼睛 ? --> 第一张 (2) | 第三张 (1) | 第二张 (1)</code>
Now our animation works for three images, let's make it work for any number (N) images. But first, we can optimize our work slightly by splitting the animation to avoid redundancy:
<code><div> <img alt="" src=""><img alt="" src=""><img alt="" src=""><img alt="" src=""> </div></code>
The code is easier to read and maintain! We make one animation for the sliding section and another animation for the z-index
update. Please note that we use z-index
on the steps(1)
animation. This is because I want to change the z-index
value suddenly, unlike the slide animation, where we want to move smoothly.
Now the code is easier to read and maintain, we can better understand how to support any number of images. What we need to do is update the percentage of animation delays and keyframes. Delay is easy because we can support multiple images in the circular slider using the exact same loop we made in the previous post:
<code>.gallery { display: grid; width: 220px; /* 控制大小 */ } .gallery > img { grid-area: 1 / 1; width: 100%; aspect-ratio: 1; object-fit: cover; border: 10px solid #f2f2f2; box-shadow: 0 0 4px #0007; }</code>
This means we are moving from normal CSS to Sass. Next, we need to imagine how the timeline changes with N images. Don't forget that animation is divided into three stages:
After "Swipe Right" and "Swipe Left", the image should remain unmoved until the rest of the images complete the sequence. Therefore, the "Direct" part takes the same time as (N - 1) as "Swipe Right" and "Swipe Left". In one iteration, N images will slide. Therefore, both Swipe Right and Swipe Left account for 100%/N of the total animation timeline. Both Swipe Right and Swipe Left account for 100%/N of the total animation timeline. The image slides out of the stack at (100%/N)/2 and slides back at 100%/N.
We can change this:
<code>.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 6s / 3 */ .gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 6s / 3 */</code>
…To this point:
<code>@keyframes slide { 0% { transform: translateX(0%); } 16.67% { transform: translateX(120%); } 33.34% { transform: translateX(0%); } 100% { transform: translateX(0%); } }</code>
If we replace N with 3, when there are 3 images in the stack, we will get 16.67% and 33.33%. The logic of stacking order is the same, we will have:
<code>@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; } /* 我们在这里更新 z-order */ 33.34% { transform: translateX(0%); z-index: 1; } 100% { transform: translateX(0% ); z-index: 1; } }</code>
We still need to update the 66.33% point. This should be where the image resets its z-index
before the animation ends. Meanwhile, the next image starts to slide. Since the sliding part requires 100%/N, the reset should happen at 100% - 100%/N:
<code>我们的眼睛 ? --> 第三张 (2) | 第二张 (2) | 第一张 (2)</code>
But in order for our z-order-last
animation to work, it should happen later in the sequence. Remember the fix we made to the last image? Resetting the z-index
value needs to happen when the first image is moved out of the stack, not when it starts to slide. We can use the same reasoning in keyframes:
<code>我们的眼睛 ? --> 第二张 (2) | 第一张 (2) | 第三张 (1)</code>
We're done! Here are the results obtained when using five images:
We can add a little spin to make things look prettier:
All I did was append rotate(var(--r))
to the transform
attribute. In a loop, --r
defines using a random angle:
<code>我们的眼睛 ? --> 第一张 (2) | 第三张 (1) | 第二张 (1)</code>
Rotation can create small failures, because we can sometimes see some images jumping behind the stack, but that doesn't matter.
All of this z-index
work is a huge balancing act, right? If you are not sure how the stacking order works before this exercise, you may now have a better understanding of it! If you find some explanations difficult to understand, I highly recommend reading this article again and drawing the content with pencil and paper. Try using different numbers of images to illustrate each step of the animation to better understand this technique.
Last time, we used some geometric tricks to create a circular slider that rotates back to the first image after completing a complete sequence. This time, we used z-index
to complete a similar technique. In either case, we did not copy any images to simulate continuous animations, nor did we use JavaScript to help with the calculations.
Next time, we will make 3D sliders. Stay tuned!
The above is the detailed content of CSS Infinite Slider Flipping Through Polaroid Images. For more information, please follow other related articles on the PHP Chinese website!