Eight words set around a drum that sits in a sentence. Hover and the drum ticks round one word at a time, like a slot reel or a date picker, with the neighbors fading off the curve.
Also known as: rotating words drum, picker wheel text css, 3d text carousel.
We
<p class="hfx-word-cylinder-text" aria-label="We design, build, test and ship">
<span>We</span>
<span class="hfx-word-cylinder-text-drum" aria-hidden="true">
<span class="hfx-word-cylinder-text-ring">
<span style="--i: 0">design</span>
<span style="--i: 1">build</span>
<span style="--i: 2">test</span>
<span style="--i: 3">ship</span>
<span style="--i: 4">learn</span>
<span style="--i: 5">iterate</span>
<span style="--i: 6">grow</span>
<span style="--i: 7">repeat</span>
</span>
</span>
</p>.hfx-word-cylinder-text {
--c: var(--hfx-color, #3b82f6);
display: flex;
align-items: center;
gap: 0.28em;
margin: 0;
font: 800 1em system-ui, sans-serif;
font-size: calc(var(--hfx-size, 16px) * 2);
cursor: default;
}
.hfx-word-cylinder-text-drum {
position: relative;
width: 4.3em;
height: 3em;
perspective: 12em;
-webkit-mask-image: linear-gradient(transparent, #000 30%, #000 70%, transparent);
mask-image: linear-gradient(transparent, #000 30%, #000 70%, transparent);
}
.hfx-word-cylinder-text-drum::before {
content: "";
position: absolute;
top: 50%;
left: -0.15em;
right: 0;
height: 1.3em;
margin-top: -0.65em;
border-radius: 0.25em;
background: color-mix(in srgb, var(--c) 12%, transparent);
}
.hfx-word-cylinder-text-ring {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform: translateZ(-1.45em) rotateX(0deg);
}
.hfx-word-cylinder-text-ring span {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1.2em;
margin-top: -0.6em;
line-height: 1.2em;
color: var(--c);
backface-visibility: hidden;
transform: rotateX(calc(var(--i) * -45deg)) translateZ(1.45em);
}
.hfx-word-cylinder-text:hover .hfx-word-cylinder-text-ring {
animation: hfx-word-cylinder-text-tick 8s cubic-bezier(0.5, 0, 0.25, 1.3) infinite;
}
@keyframes hfx-word-cylinder-text-tick {
0% { transform: translateZ(-1.45em) rotateX(0deg); }
4%, 12.5% { transform: translateZ(-1.45em) rotateX(45deg); }
16.5%, 25% { transform: translateZ(-1.45em) rotateX(90deg); }
29%, 37.5% { transform: translateZ(-1.45em) rotateX(135deg); }
41.5%, 50% { transform: translateZ(-1.45em) rotateX(180deg); }
54%, 62.5% { transform: translateZ(-1.45em) rotateX(225deg); }
66.5%, 75% { transform: translateZ(-1.45em) rotateX(270deg); }
79%, 87.5% { transform: translateZ(-1.45em) rotateX(315deg); }
91.5%, 100% { transform: translateZ(-1.45em) rotateX(360deg); }
}
@media (prefers-reduced-motion: reduce) {
.hfx-word-cylinder-text, .hfx-word-cylinder-text *, .hfx-word-cylinder-text::before, .hfx-word-cylinder-text::after, .hfx-word-cylinder-text *::before, .hfx-word-cylinder-text *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--hfx-color on the element or a parent.
Currently previewing #3b82f6.--hfx-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.