crafted animation framework and testing environment for designers and developers
<p class="texts-1-1-text split-text masked-text-letters">
crafted animation framework and testing environment for designers and developers
</p>
.texts-1-1 {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
&-text {
color: var(--text-main);
font-size: 4rem;
text-transform: uppercase;
text-align: center;
max-width: 96rem;
}
}
import gsap from "gsap";
import ScrollTrigger from "gsap/ScrollTrigger";
import CustomEase from "gsap/CustomEase";
import SplitText from "gsap/SplitText";
gsap.registerPlugin(SplitText, ScrollTrigger, CustomEase);
export function initializeMaskedTextLetters() {
// Split text into words and characters to prevent word breaks
const split = new SplitText(".masked-text-letters", { type: "words,chars" });
const words = split.words;
const chars = split.chars;
// Ensure words stay together on one line
words.forEach(word => {
word.style.display = 'inline-block';
word.style.whiteSpace = 'nowrap';
word.style.wordBreak = 'keep-all';
word.style.overflowWrap = 'normal';
word.style.hyphens = 'none';
});
// Wrap each char in a clipping container
chars.forEach(char => {
const wrapper = document.createElement('div');
wrapper.style.display = 'inline-block';
wrapper.style.overflow = 'hidden';
wrapper.style.visibility = 'hidden';
char.parentNode.replaceChild(wrapper, char);
wrapper.appendChild(char);
});
// Optionally refresh ScrollTrigger after DOM changes
ScrollTrigger.refresh();
// Define a custom ease
CustomEase.create("customEase", "0.2, 0.6, 0.35, 1");
// Animate each character
gsap.fromTo(
chars,
{
y: "110%"
},
{
visibility: "visible",
y: "0%",
ease: "customEase",
delay: 1.2,
duration: 0.4,
stagger: 0.01,
yoyo: true,
scrollTrigger: {
trigger: ".masked-text-letters",
start: "top bottom-=20%",
markers: {startColor: "green", endColor: "transparent"},
toggleActions: "play none none reverse",
invalidateOnRefresh: true
}
}
);
}