Each word falls into the headline, squashes on impact, hops once and settles, while the old word drops out below. The squash is a scale on a bottom transform-origin, so the word lands on its baseline.
Also known as: bounce word swap, dropping words animation, squash and stretch text.
<div class="txt-bouncing-rotating-words">Fun for
<span class="txt-bouncing-rotating-words-words">
<span style="--i: 0">kids</span>
<span style="--i: 1">teams</span>
<span style="--i: 2">everyone</span>
</span>
</div>.txt-bouncing-rotating-words {
--c: var(--txt-color, #3b82f6);
font-size: var(--txt-size, 32px);
font-family: system-ui, sans-serif;
font-weight: 700;
line-height: 1.2;
display: flex;
align-items: baseline;
gap: 0.28em;
white-space: nowrap;}
.txt-bouncing-rotating-words-words {
display: inline-grid;
color: var(--c);
}
.txt-bouncing-rotating-words-words span {
grid-area: 1 / 1;
justify-self: start;
transform-origin: 50% 100%;
/* 3 words x 2.2s = 6.6s */
animation: txt-bouncing-rotating-words-drop 6.6s infinite;
animation-delay: calc(var(--i) * 2.2s - 7.6s);
}
.txt-bouncing-rotating-words-words span:not(:first-child) {
opacity: 0;
}
@keyframes txt-bouncing-rotating-words-drop {
0% {
opacity: 0; transform: translateY(-1.1em) scale(0.9, 1.12);
animation-timing-function: cubic-bezier(0.5, 0, 0.9, 0.5);
}
3.03% { opacity: 1; }
5.3% {
transform: translateY(0) scale(1.18, 0.82);
animation-timing-function: cubic-bezier(0.2, 0.6, 0.4, 1);
}
7.58% {
transform: translateY(-0.2em) scale(0.96, 1.05);
animation-timing-function: cubic-bezier(0.5, 0, 0.9, 0.5);
}
9.7% { transform: translateY(0) scale(1.06, 0.94); }
11.52% { transform: translateY(0) scale(1); }
28.79% {
opacity: 1; transform: translateY(0) scale(1);
animation-timing-function: cubic-bezier(0.5, 0, 0.9, 0.5);
}
33.33% { opacity: 0; transform: translateY(0.6em) scale(1); }
100% { opacity: 0; transform: translateY(-1.1em) scale(0.9, 1.12); }
}
@media (prefers-reduced-motion: reduce) {
.txt-bouncing-rotating-words, .txt-bouncing-rotating-words *, .txt-bouncing-rotating-words::before, .txt-bouncing-rotating-words::after, .txt-bouncing-rotating-words *::before, .txt-bouncing-rotating-words *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--txt-color on the element or a parent.
Currently previewing #3b82f6.--txt-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 40px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.