Highlight Text on Scroll
Characters in a heading fade from a low opacity to fully visible as the user scrolls, creating a smooth word-by-word highlight effect driven entirely by scroll position. No pseudo-elements or duplicate DOM nodes required.
GSAPScrollTriggerSplitTextScrollTypography
Setup — External Scripts
GSAP CDN
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>ScrollTrigger CDN
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/ScrollTrigger.min.js"></script>SplitText CDN
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/SplitText.min.js"></script>Code
index.html
html
<h1 data-highlight-text>Add your heading here</h1>script.js
javascript
gsap.registerPlugin(ScrollTrigger, SplitText)
function initHighlightText() {
document.querySelectorAll("[data-highlight-text]").forEach(heading => {
const scrollStart = heading.getAttribute("data-highlight-scroll-start") || "top 90%"
const scrollEnd = heading.getAttribute("data-highlight-scroll-end") || "center 40%"
const fadedValue = heading.getAttribute("data-highlight-fade") || 0.2
const staggerValue = heading.getAttribute("data-highlight-stagger") || 0.1
new SplitText(heading, {
type: "words, chars",
autoSplit: true,
onSplit(self) {
let ctx = gsap.context(() => {
let tl = gsap.timeline({
scrollTrigger: {
scrub: true,
trigger: heading,
start: scrollStart,
end: scrollEnd,
}
})
tl.from(self.chars, {
autoAlpha: fadedValue,
stagger: staggerValue,
ease: "linear"
})
});
return ctx;
}
});
});
}
document.addEventListener("DOMContentLoaded", () => {
initHighlightText();
});Notes
- •`data-highlight-fade` sets the starting opacity of un-highlighted characters (default 0.2). `data-highlight-stagger` controls the offset between characters — lower values create a smoother wave, higher values highlight one letter at a time.
- •`autoSplit: true` re-splits the text on resize so line wrapping is always correct; the returned GSAP context kills the old ScrollTrigger before a new one is created.
- •Because the animation uses `scrub: true`, the highlight follows scroll position directly — scrolling back up un-highlights the text.
- •No pseudo-elements, duplicated text, or clip masks are needed — just a straight opacity tween on each character span.
Guide
Customisation attributes
All attributes are optional and fall back to sensible defaults. Add them to the same element as `data-highlight-text`.
<h1
data-highlight-text
data-highlight-scroll-start="top 90%"
data-highlight-scroll-end="center 40%"
data-highlight-fade="0.2"
data-highlight-stagger="0.1"
>
Your heading here
</h1>