Scroll-based text color animation - how to animate line-by-line instead of entire block?

3 days ago 6
ARTICLE AD BOX

I’m trying to recreate the scroll-based text animation used on SXSW’s website, where text transitions from gray to black as the user scrolls.

What I want:

- Text should animate line-by-line
- Each line should fill from left → right
- Lines should animate sequentially (like reading)
(Line 1 finishes → then Line 2 starts → etc.)

Problem:

My current implementation either:
- Animates all lines at once, or
- Animates vertically (top - bottom), but not per-line correctly

I can’t get the behavior where only one line animates at a time.
Expected behavior:
- Only one line animates at a time (similar to https://sxsw.com/ - sxsw is 850+ section)
- Each line fills left → right
- Next line starts only after previous finishes

Here is my code:
document.addEventListener("DOMContentLoaded", () => { const quote = document.querySelector(".quote-text"); if (!quote) return; // ---------------------------- // STEP 1: Split text into lines // ---------------------------- const text = quote.innerText.trim(); quote.innerHTML = ""; const words = text.split(" "); let lineSpan = document.createElement("span"); lineSpan.className = "line"; quote.appendChild(lineSpan); let lineText = ""; words.forEach((word) => { const testLine = lineText + word + " "; lineSpan.textContent = testLine; if (lineSpan.scrollWidth > quote.clientWidth) { lineText = word + " "; lineSpan = document.createElement("span"); lineSpan.className = "line"; lineSpan.textContent = lineText; quote.appendChild(lineSpan); } else { lineText = testLine; } }); const lines = quote.querySelectorAll(".line"); // ---------------------------- // STEP 2: Scroll animation (STRICT SEQUENCE) // ---------------------------- function updateProgress() { const rect = quote.getBoundingClientRect(); const windowHeight = window.innerHeight; let progress = (windowHeight - rect.top) / (windowHeight + rect.height); progress = Math.min(Math.max(progress, 0), 1); const totalLines = lines.length; // 👉 Determine which line is active const currentLineIndex = Math.floor(progress * totalLines); lines.forEach((line, i) => { if (i < currentLineIndex) { // fully completed lines line.style.setProperty("--progress", "100%"); } else if (i === currentLineIndex) { // active line (animate left → right) const lineProgress = (progress * totalLines) - i; line.style.setProperty("--progress", `${lineProgress * 100}%`); } else { // not started yet line.style.setProperty("--progress", "0%"); } }); } window.addEventListener("scroll", updateProgress); window.addEventListener("resize", updateProgress); updateProgress(); }); .quote-text .line { display: block; --progress: 0%; background: linear-gradient( to right, #000 var(--progress), #bbb var(--progress) ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } <section> <blockquote class="quote-text"> Community banks play a critical role in expanding access to financial services and supporting the economic vitality of the communities they serve. Ongoing education for directors and senior management strengthens board oversight and promotes effective governance. </blockquote> </section>
Read Entire Article