Letter Stagger Hover

Button
Hover
Text Animation
CSS
GSAP
Split Text
JS
Last Updated: Nov 18, 2025
  • clip-path: polygon(0 9%, 100% 9%, 100% 110%, 0 110%); in custom code sets the mask position. It starts 9% from the top of the text and ends 110% from the top. You may need to adjust these values depending on the font that's used.
  • Background color change on hover is set natively in the style panel on hover-6_component
<style>
.hover-6_component .word-mask {
	overflow: visible !important;
	clip-path: polygon(0 9%, 100% 9%, 100% 110%, 0 110%);
}
.hover-6_component .word {
	white-space: nowrap;
}
@media (hover: none) {
	.hover-6_component .word > * { transform: unset !important; opacity: 1 !important; }
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/SplitText.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
	gsap.registerPlugin(SplitText);
	function init(component, instant = false) {
		if (component.hasAttribute("data-hover-6")) return;
		component.setAttribute("data-hover-6", "");
    
		const text1 = component.querySelector(".hover-6_text.is-1");
		const text2 = component.querySelector(".hover-6_text.is-2");
		if (!text1 || !text2) return;
		const split1 = SplitText.create(text1, { type: "words, chars", mask: "words", wordsClass: "word" });
		const split2 = SplitText.create(text2, { type: "words, chars", mask: "words", wordsClass: "word", aria: "none" });
		const moveAmount = 120;
		function hoverIn() {
			let tl = gsap.timeline({ 
				defaults: { 
					duration: 0.3,
					ease: "power2.inOut",
					stagger: { from: "start", each: 0.015, ease: "power1.out" }
				}
			});
			tl.fromTo(split1.chars, { yPercent: 0, opacity: 1 }, { yPercent: moveAmount * -1, opacity: 0 });
			tl.fromTo(split2.chars, { yPercent: moveAmount, opacity: 0 }, { yPercent: 0, opacity: 1 }, 0);
		}
		function hoverOut() {
			let tl = gsap.timeline({ 
				defaults: { 
					duration: 0.2,
					ease: "power1.inOut",
					stagger: { from: "start", each: 0.01, ease: "power1.in" }
				}
			});
			tl.to(split1.chars, { yPercent: 0, opacity: 1 });
			tl.to(split2.chars, { yPercent: moveAmount, opacity: 0 }, 0);
		}
		if (instant) hoverIn();
		component.addEventListener("mouseenter", hoverIn);
		component.addEventListener("mouseleave", hoverOut);
	}
	document.querySelectorAll(".hover-6_component").forEach(init);
	document.addEventListener("mouseover", (e) => {
		if (!(e.target instanceof Element)) return;
		const component = e.target.closest(".hover-6_component");
		if (!component) return;
		init(component, true);
	});
});
</script>