A tilted ring of words circles a bold center word in 3D, passing in front and behind it, fading as each word goes round the back. One registered angle turns the ring and keeps every word facing you.
Also known as: 3d spinning text ring, text orbit animation css, words rotating around text, 3d text carousel.
<div class="txt-orbiting-word-ring" aria-label="Stack: Auth, Billing, Search, Email, Storage, Analytics"> <span class="txt-orbiting-word-ring-ring" aria-hidden="true"><b>Stack</b><span style="--i: 0">Auth</span><span style="--i: 1">Billing</span><span style="--i: 2">Search</span><span style="--i: 3">Email</span><span style="--i: 4">Storage</span><span style="--i: 5">Analytics</span></span> </div>
@property --txt-orbiting-word-ring-a {
syntax: "<angle>";
inherits: true;
initial-value: 0deg;
}
.txt-orbiting-word-ring {
--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: grid;
place-items: center;
width: 15.4em;
height: 6.4em;
perspective: 22em;}
.txt-orbiting-word-ring-ring {
position: relative;
transform-style: preserve-3d;
transform: rotateX(72deg) rotateZ(var(--txt-orbiting-word-ring-a));
animation: txt-orbiting-word-ring-turn 18s linear infinite;
}
/* the orbit itself: a circle in the tilted plane reads as an ellipse */
.txt-orbiting-word-ring-ring::before {
content: "";
position: absolute;
left: -5.6em;
top: -5.6em;
width: 11.2em;
height: 11.2em;
border-radius: 50%;
border: 1px solid color-mix(in srgb, var(--c) 40%, transparent);
}
.txt-orbiting-word-ring-ring b,
.txt-orbiting-word-ring-ring span {
position: absolute;
left: 0;
top: 0;
white-space: nowrap;
transform-origin: 0 0;
}
.txt-orbiting-word-ring-ring b {
font-size: 1.5em;
font-weight: 800;
letter-spacing: -0.02em;
transform: rotateZ(calc(-1 * var(--txt-orbiting-word-ring-a))) rotateX(-72deg) translate(-50%, -50%);
}
.txt-orbiting-word-ring-ring span {
font-size: 0.78em;
font-weight: 600;
color: var(--c);
/* out to the orbit, then undo the spin and the tilt so the word faces you */
transform:
rotateZ(calc(var(--i) * 60deg)) translateY(-7.18em)
rotateZ(calc(var(--i) * -60deg - var(--txt-orbiting-word-ring-a))) rotateX(-72deg) translate(-50%, -50%);
opacity: calc(0.62 - 0.38 * cos(var(--i) * 60deg + var(--txt-orbiting-word-ring-a)));
}
@keyframes txt-orbiting-word-ring-turn {
to { --txt-orbiting-word-ring-a: 360deg; }
}
@media (prefers-reduced-motion: reduce) {
.txt-orbiting-word-ring-ring { animation: none; --txt-orbiting-word-ring-a: 30deg; }
}
@media (prefers-reduced-motion: reduce) {
.txt-orbiting-word-ring, .txt-orbiting-word-ring *, .txt-orbiting-word-ring::before, .txt-orbiting-word-ring::after, .txt-orbiting-word-ring *::before, .txt-orbiting-word-ring *::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.