I’m looking to make a word rotator, whereby an unknown number of words slide in for 1 second, pause for 3 seconds, then slide out for 1 second, one after another, then repeat, using CSS if possible. So the overall loop should be 5 seconds duration.
I’m using CSS custom properties to set an index on each item and use that to calculate the animation delay. However, because there is an unknown number of words I don’t think that it’s possible to achieve what I need in CSS alone, as the number of words effects the overall duration of the animation, which in turn affects the keyframe percentage breakdown.
The code I have is this:
<p class="rotator" style="--count: 6; --delay: 5s;">
<span class="rotator__item" style="--i: 0">awesome</span>
<span class="rotator__item" style="--i: 1">amazing</span>
<span class="rotator__item" style="--i: 2">legendary</span>
<span class="rotator__item" style="--i: 3">super duper</span>
<span class="rotator__item" style="--i: 4">fantistic</span>
<span class="rotator__item" style="--i: 5">fun</span>
</p>
<p class="rotator" style="--count: 3; --delay: 5s;">
<span class="rotator__item" style="--i: 0">awesome</span>
<span class="rotator__item" style="--i: 1">amazing</span>
<span class="rotator__item" style="--i: 2">legendary</span>
</p>
.rotator {
--duration: calc(var(--count) * var(--delay));
white-space: nowrap;
position: relative;
height: 100px;
}
.rotator__item {
position: absolute;
top: 0;
opacity: 0;
animation: fadeSlide var(--delay) infinite;
animation-delay: calc(var(--i) * 5s);
}
@keyframes fadeSlide {
0% {
transform: translateY(-50%);
opacity: 0;
}
10% {
transform: translateY(0);
opacity: 1;
}
70% {
transform: translateY(0);
opacity: 1;
}
80% {
transform: translateY(50%);
opacity: 0;
}
100% {
transform: translateY(50%);
opacity: 0;
}
}
Clearly this doesn’t work as the delay isn’t correct so the items sequentially overlap one another, but then increasing the delay to cater for the number of items (e.g. animation: fadeSlide calc(var(--delay) * var(--count)) infinite;
) corrects the stagger but breaks the animation because the keyframe %s are no longer appropriate.
I feel like the answer here is “it can’t be done in CSS alone” but I wanted to check in case anyone knew of a way to achieve this without resorting to JavaScript.
3
One way of doing this is to have the words stacked on top of each other (in the z-axis sense) and show just the top one and (partially) the second one.
Then all the JS has to do is every 5 seconds move the top one down to the bottom, which can be done with an append.
CSS takes care of the rest with an animation on first-child and an animation on second child. (so the second word starts to roll in just before the first has disappeared).
Obviously you will want to play with the timing parameters to get exactly the feeling of rotation that you want, but this method does save you having to generate a mass of CSS, and it is self-adjusting no matter how many words you have.
<p class="rotator">
<span class="rotator__item">awesome</span>
<span class="rotator__item">amazing</span>
<span class="rotator__item">legendary</span>
<span class="rotator__item">super duper</span>
<span class="rotator__item">fantistic</span>
<span class="rotator__item">fun</span>
</p>
<p class="rotator">
<span class="rotator__item">awesome</span>
<span class="rotator__item">amazing</span>
<span class="rotator__item">legendary</span>
</p>
<style>
.rotator {
white-space: nowrap;
position: relative;
height: 100px;
}
.rotator>* {
opacity: 0;
}
.rotator>*:first-child {
opacity: 1;
}
.rotator__item {
position: absolute;
top: 0;
opacity: 0;
}
@keyframes fadeSlide {
0% {
transform: translateY(0%);
opacity: 1;
}
75% {
transform: translateY(0);
opacity: 1;
}
90% {
opacity: 0.1;
}
100% {
transform: translateY(100%);
opacity: 0;
}
}
.rotator>*:first-child {
animation: fadeSlide 4s 1 forwards;
}
.rotator>*:nth-child(2) {
animation: fadeSlide2 1s 1 forwards 3s;
}
@keyframes fadeSlide2 {
0% {
transform: translateY(-100%);
opacity: 0;
}
80% {
opacity: 1;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
</style>
<script>
const rotators = document.querySelectorAll('.rotator');
rotators.forEach((rotator) => setInterval(function() {
rotator.append(rotator.querySelector('*:first-child'));
}, 5000));
</script>