Big outlined words scroll past and fill with your accent as they cross the middle, then hollow out again on the way off. Two synced marquee layers, the filled one masked to a soft window.
Also known as: outline text marquee css, scrolling text outline fill, big marquee text animation.
<div class="txt-outline-fill-marquee" aria-label="Design, build, ship, repeat"> <div class="txt-outline-fill-marquee-layer" aria-hidden="true"><div><span>Design</span><i></i><span>Build</span><i></i><span>Ship</span><i></i><span>Repeat</span><i></i><span>Design</span><i></i><span>Build</span><i></i><span>Ship</span><i></i><span>Repeat</span><i></i></div></div> <div class="txt-outline-fill-marquee-layer" aria-hidden="true"><div><span>Design</span><i></i><span>Build</span><i></i><span>Ship</span><i></i><span>Repeat</span><i></i><span>Design</span><i></i><span>Build</span><i></i><span>Ship</span><i></i><span>Repeat</span><i></i></div></div> </div>
.txt-outline-fill-marquee {
--c: var(--txt-color, #3b82f6);
font-size: calc(var(--txt-size, 32px) * 2);
font-family: system-ui, sans-serif;
font-weight: 800;
line-height: 1.2;
display: grid;
grid-template-columns: minmax(0, 1fr);
width: 7.4em;
overflow: hidden;
letter-spacing: -0.03em;
-webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);}
.txt-outline-fill-marquee-layer { grid-area: 1 / 1; }
.txt-outline-fill-marquee-layer > div {
display: flex;
align-items: center;
width: max-content;
animation: txt-outline-fill-marquee-scroll 13s linear infinite;
}
.txt-outline-fill-marquee-layer span,
.txt-outline-fill-marquee-layer i { margin-right: 0.32em; }
.txt-outline-fill-marquee-layer span {
/* a static face: variable fonts show their overlapping contours when stroked */
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-text-fill-color: transparent;
-webkit-text-stroke: 0.024em color-mix(in srgb, currentColor 42%, transparent);
}
/* four-point star separators */
.txt-outline-fill-marquee-layer i {
width: 0.34em;
height: 0.34em;
background: color-mix(in srgb, currentColor 30%, transparent);
clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
}
/* the filled copy only shows through a soft window in the middle */
.txt-outline-fill-marquee-layer + .txt-outline-fill-marquee-layer {
-webkit-mask-image: linear-gradient(90deg, transparent 30%, #000 43%, #000 57%, transparent 70%);
mask-image: linear-gradient(90deg, transparent 30%, #000 43%, #000 57%, transparent 70%);
}
.txt-outline-fill-marquee-layer + .txt-outline-fill-marquee-layer span { -webkit-text-fill-color: var(--c); -webkit-text-stroke: 0.024em var(--c); }
.txt-outline-fill-marquee-layer + .txt-outline-fill-marquee-layer i { background: var(--c); }
@keyframes txt-outline-fill-marquee-scroll {
to { translate: -50% 0; }
}
@media (prefers-reduced-motion: reduce) {
.txt-outline-fill-marquee-layer > div { animation: none; translate: -0.6em 0; }
}
@media (prefers-reduced-motion: reduce) {
.txt-outline-fill-marquee, .txt-outline-fill-marquee *, .txt-outline-fill-marquee::before, .txt-outline-fill-marquee::after, .txt-outline-fill-marquee *::before, .txt-outline-fill-marquee *::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.