Highlight Marker Text Reveal

Documentation
Webflow
Code
Setup: External Scripts
External Scripts in Webflow
Make sure to always put the External Scripts before the JavasScript step of the resource. In this video you learn where to put these in your Webflow project:
HTML
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/SplitText.min.js"></script>Step 1: Copy structure to Webflow
Copy structure to Webflow
In the video below we described how you can copy + paste the structure of this resource to your Webflow project:
Copy to Webflow
Webflow structure is not required for this resource.
Step 1: Add HTML
HTML
<h1 data-highlight-marker-reveal data-marker-direction="right" data-marker-theme="pink" class="highlight-title">Here's a text reveal that looks like a highlight marker</h1>HTML structure is not required for this resource.
Step 2: Add CSS
CSS
.highlight-title {
text-align: center;
letter-spacing: -0.03em;
text-transform: uppercase;
margin-top: 0;
margin-bottom: 0;
font-family: Haffer, Arial, sans-serif;
font-size: 6vw;
font-weight: 900;
line-height: 0.9;
}
[data-highlight-marker-reveal]{
visibility: hidden;
}
[data-highlight-marker-reveal] .highlight-marker-line{
width: auto;
display: inline-block !important;
margin: -0.055em 0px;
}
.highlight-marker-bar{
position: absolute;
inset: -0.055em 0px;
z-index: 1;
pointer-events: none;
}Step 2: Add custom Javascript
Custom Javascript in Webflow
In this video, Ilja gives you some guidance about using JavaScript in Webflow:
Step 2: Add Javascript
Step 3: Add Javascript
Javascript
function initHighlightMarkerTextReveal() {
const defaults = {
direction: "right",
theme: "pink",
scrollStart: "top 90%",
staggerStart: "start",
stagger: 100,
barDuration: 0.6,
barEase: "power3.inOut",
};
const colorMap = {
pink: "#C700EF",
white: "#FFFFFF",
};
const directionMap = {
right: { prop: "scaleX", origin: "right center" },
left: { prop: "scaleX", origin: "left center" },
up: { prop: "scaleY", origin: "center top" },
down: { prop: "scaleY", origin: "center bottom" },
};
function resolveColor(value) {
if (colorMap[value]) return colorMap[value];
if (value.startsWith("--")) {
return getComputedStyle(document.body).getPropertyValue(value).trim() || value;
}
return value;
}
function createBar(color, origin) {
const bar = document.createElement("div");
bar.className = "highlight-marker-bar";
Object.assign(bar.style, {
backgroundColor: color,
transformOrigin: origin,
});
return bar;
}
function cleanupElement(el) {
if (!el._highlightMarkerReveal) return;
el._highlightMarkerReveal.timeline?.kill();
el._highlightMarkerReveal.scrollTrigger?.kill();
el._highlightMarkerReveal.split?.revert();
el.querySelectorAll(".highlight-marker-bar").forEach((bar) => bar.remove());
delete el._highlightMarkerReveal;
}
let reduceMotion = false;
gsap.matchMedia().add(
{ reduce: "(prefers-reduced-motion: reduce)" },
(context) => {
reduceMotion = context.conditions.reduce;
}
);
// Reduced motion: no animation at all
if (reduceMotion) {
document.querySelectorAll("[data-highlight-marker-reveal]").forEach((el) => {
gsap.set(el, { autoAlpha: 1 });
});
return;
}
// Cleanup previous instances
document.querySelectorAll("[data-highlight-marker-reveal]").forEach(cleanupElement);
const elements = document.querySelectorAll("[data-highlight-marker-reveal]");
if (!elements.length) return;
elements.forEach((el) => {
const direction = el.getAttribute("data-marker-direction") || defaults.direction;
const theme = el.getAttribute("data-marker-theme") || defaults.theme;
const scrollStart = el.getAttribute("data-marker-scroll-start") || defaults.scrollStart;
const staggerStart = el.getAttribute("data-marker-stagger-start") || defaults.staggerStart;
const staggerOffset = (parseFloat(el.getAttribute("data-marker-stagger")) || defaults.stagger) / 1000;
const color = resolveColor(theme);
const dirConfig = directionMap[direction] || directionMap.right;
el._highlightMarkerReveal = {};
const split = SplitText.create(el, {
type: "lines",
linesClass: "highlight-marker-line",
autoSplit: true,
onSplit(self) {
const instance = el._highlightMarkerReveal;
// Teardown previous build
instance.timeline?.kill();
instance.scrollTrigger?.kill();
el.querySelectorAll(".highlight-marker-bar").forEach((bar) => bar.remove());
// Build bars and timeline
const lines = self.lines;
const tl = gsap.timeline({ paused: true });
lines.forEach((line, i) => {
gsap.set(line, { position: "relative", overflow: "hidden" });
const bar = createBar(color, dirConfig.origin);
line.appendChild(bar);
const staggerIndex = staggerStart === "end" ? lines.length - 1 - i : i;
tl.to(bar, {
[dirConfig.prop]: 0,
duration: defaults.barDuration,
ease: defaults.barEase,
}, staggerIndex * staggerOffset);
});
// Reveal parent — bars are covering the text
gsap.set(el, { autoAlpha: 1 });
// ScrollTrigger
const st = ScrollTrigger.create({
trigger: el,
start: scrollStart,
once: true,
onEnter: () => tl.play(),
});
instance.timeline = tl;
instance.scrollTrigger = st;
},
});
el._highlightMarkerReveal.split = split;
});
}
// Initialize Highlight Marker Text Reveal
document.addEventListener("DOMContentLoaded", () => {
document.fonts.ready.then(() => {
initHighlightMarkerTextReveal();
});
});Step 3: Add custom CSS
Step 2: Add custom CSS
Custom CSS in Webflow
Curious about where to put custom CSS in Webflow? Ilja explains it in the below video:
CSS
[data-highlight-marker-reveal]{
visibility: hidden;
}
.wf-design-mode [data-highlight-marker-reveal]{
visibility: visible;
}
[data-highlight-marker-reveal] .highlight-marker-line{
width: auto;
display: inline-block !important;
margin: -0.055em 0px;
}
.highlight-marker-bar{
position: absolute;
inset: -0.055em 0px;
z-index: 1;
pointer-events: none;
}Implementation
Targets
Add [data-highlight-marker-reveal] to any text element to activate the highlight marker reveal. Each line of text will be covered by a colored bar that scales away on scroll, revealing the content beneath. The script uses GSAP SplitText to split the text into lines, so no manual line wrapping is needed.
Direction
Control which way the bar moves away with [data-marker-direction] (default right). Accepted values are left, right, up, and down, the bar anchors to the named edge and scales toward it, revealing text from the opposite side.
Theme
The bar color is set through [data-marker-theme] (default pink), which accepts a named color key, a CSS custom property, or any raw color value. Named keys resolve through a color map defined in the JavaScript. You can add as many options in here as you want.
const colorMap = {
pink: "#C700EF",
white: "#FFFFFF",
};To read a CSS variable from the body, pass the variable name including the dashes:
<h2 data-highlight-marker-reveal data-marker-theme="--brand-accent">Heading</h2>Any value that isn't a named key or CSS variable is used as-is, so #ff6600, rgb(255, 102, 0), or any valid CSS color works directly.
Scroll Start
The scroll trigger fires by default when the element reaches 90% of the viewport. Override this per element with [data-marker-scroll-start], which accepts any valid ScrollTrigger start value.
Stagger
Lines animate in sequence with a default offset of 100ms between each line. Adjust this per element with [data-marker-stagger] in milliseconds. A value of 500 for example will make each bar stagger with 0.5s of delay.
Stagger Start
By default, lines reveal from top to bottom. You can flip the order, and make the sequence move bottom-to-top by adding [data-marker-stagger-start="end"] to the target.
Bar Height and Spacing
The visible height of the bars and the spacing between them is controlled entirely through CSS. Three properties work together: the line-height on the text element, and the inset and margin values on the line and bar.
[data-highlight-marker-reveal] .highlight-marker-line {
margin: -0.055em 0px;
}
.highlight-marker-bar {
inset: -0.055em 0px;
}The negative margin on .highlight-marker-line pulls lines closer together, while the negative top/bottom inset on .highlight-marker-bar extends the bar beyond the line box. Together with the element's line-height, these three values determine whether bars overlap, touch seamlessly, or have visible gaps between them. Increase the negative values to create taller bars that bleed into adjacent lines, or reduce them to leave breathing room between each highlighted row.
Defaults
All attributes are optional. When omitted, values fall back to a defaults object defined at the top of the script. Adjust these to change the baseline behavior across all instances at once.
const defaults = {
direction: "right",
theme: "pink",
scrollStart: "top 90%",
staggerStart: "start",
stagger: 100,
barDuration: 0.6,
barEase: "power3.inOut",
};The barDuration and barEase are only configurable through this object and don't have corresponding attributes, they're meant to stay consistent across the page.
Resource details
Last updated
February 18, 2026
Popularity
84 views
Category
Text Animations
Need help?
Join Slack








