masked-reveal
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMasked Reveal
遮罩式渐显
Use When
适用场景
- A headline or short text block needs a premium reveal on scroll.
- Words should rise through an invisible mask with a staggered sequence.
- The project already uses GSAP or needs ScrollTrigger-based motion.
- 标题或短文本块需要在滚动时呈现高级感的渐显效果。
- 文字需通过不可见遮罩按顺序逐字向上渐显。
- 项目已使用GSAP,或需要基于ScrollTrigger的动效。
Motion Defaults
动效默认参数
- Trigger: start when the text top reaches of the viewport.
82% - Duration: to
0.7s.0.9s - Stagger: to
0.025sper word.0.045s - Offset: to
yPercent: 110.0 - Ease: or
power3.out.expo.out - Replay: reveal once by default.
- 触发条件:当文本顶部到达视口的82%位置时启动。
- 时长:0.7秒至0.9秒。
- 延迟间隔:每个文字0.025秒至0.045秒。
- 偏移量:yPercent从110变为0。
- 缓动效果:power3.out或expo.out。
- 重播:默认仅触发一次渐显。
HTML
HTML
html
<h1 class="masked-reveal" data-masked-reveal>
Design systems that feel alive from the first scroll.
</h1>html
<h1 class="masked-reveal" data-masked-reveal>
Design systems that feel alive from the first scroll.
</h1>CSS Mask
CSS遮罩
css
.masked-reveal {
visibility: visible;
}
html.js .masked-reveal[data-masked-reveal] {
visibility: hidden;
}
html.js .masked-reveal.is-split {
visibility: visible;
}
.masked-reveal .word-mask {
display: inline-block;
overflow: hidden;
vertical-align: top;
}
.masked-reveal .word {
display: inline-block;
transform: translateY(110%);
will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
html.js .masked-reveal[data-masked-reveal] {
visibility: visible;
}
.masked-reveal .word {
transform: none;
}
}css
.masked-reveal {
visibility: visible;
}
html.js .masked-reveal[data-masked-reveal] {
visibility: hidden;
}
html.js .masked-reveal.is-split {
visibility: visible;
}
.masked-reveal .word-mask {
display: inline-block;
overflow: hidden;
vertical-align: top;
}
.masked-reveal .word {
display: inline-block;
transform: translateY(110%);
will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
html.js .masked-reveal[data-masked-reveal] {
visibility: visible;
}
.masked-reveal .word {
transform: none;
}
}GSAP ScrollTrigger
GSAP ScrollTrigger
This helper avoids the paid SplitText plugin and keeps spaces intact.
js
document.documentElement.classList.add("js");
gsap.registerPlugin(ScrollTrigger);
function escapeHTML(value) {
return value
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function splitMaskedReveal(element) {
if (element.dataset.maskedRevealReady === "true") return;
const text = element.textContent.trim();
element.setAttribute("aria-label", text);
element.innerHTML = text
.split(/(\s+)/)
.map((part) => {
if (!part.trim()) return part;
return `<span class="word-mask" aria-hidden="true"><span class="word">${escapeHTML(part)}</span></span>`;
})
.join("");
element.dataset.maskedRevealReady = "true";
element.classList.add("is-split");
}
function initMaskedReveals(selector = "[data-masked-reveal]") {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
document.querySelectorAll(selector).forEach((element) => {
splitMaskedReveal(element);
const words = element.querySelectorAll(".word");
gsap.set(element, { autoAlpha: 1 });
gsap.fromTo(
words,
{ yPercent: 110 },
{
yPercent: 0,
duration: 0.8,
ease: "power3.out",
stagger: 0.035,
scrollTrigger: {
trigger: element,
start: "top 82%",
once: true,
},
}
);
});
}
initMaskedReveals();这个工具类无需付费的SplitText插件,同时能完整保留空格。
js
document.documentElement.classList.add("js");
gsap.registerPlugin(ScrollTrigger);
function escapeHTML(value) {
return value
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function splitMaskedReveal(element) {
if (element.dataset.maskedRevealReady === "true") return;
const text = element.textContent.trim();
element.setAttribute("aria-label", text);
element.innerHTML = text
.split(/(\s+)/)
.map((part) => {
if (!part.trim()) return part;
return `<span class="word-mask" aria-hidden="true"><span class="word">${escapeHTML(part)}</span></span>`;
})
.join("");
element.dataset.maskedRevealReady = "true";
element.classList.add("is-split");
}
function initMaskedReveals(selector = "[data-masked-reveal]") {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
document.querySelectorAll(selector).forEach((element) => {
splitMaskedReveal(element);
const words = element.querySelectorAll(".word");
gsap.set(element, { autoAlpha: 1 });
gsap.fromTo(
words,
{ yPercent: 110 },
{
yPercent: 0,
duration: 0.8,
ease: "power3.out",
stagger: 0.035,
scrollTrigger: {
trigger: element,
start: "top 82%",
once: true,
},
}
);
});
}
initMaskedReveals();React Cleanup Pattern
React 清理模式
js
useLayoutEffect(() => {
const ctx = gsap.context(() => {
initMaskedReveals("[data-masked-reveal]");
}, rootRef);
return () => ctx.revert();
}, []);js
useLayoutEffect(() => {
const ctx = gsap.context(() => {
initMaskedReveals("[data-masked-reveal]");
}, rootRef);
return () => ctx.revert();
}, []);Taste Rules
风格准则
- Use on short headlines, labels, and section intros; avoid long paragraphs.
- Keep the vertical offset clean. Do not combine with blur unless the style explicitly calls for it.
- Stagger by word, not letter, for a calmer editorial feel.
- Initialize after fonts are loaded if line wrapping is critical.
- Use after late-loading images or layout shifts.
ScrollTrigger.refresh() - Do not split text that contains links, buttons, or meaningful inline markup.
- 用于短标题、标签和章节引言;避免长段落。
- 保持垂直偏移简洁。除非风格明确要求,否则不要结合模糊效果。
- 按文字而非字母设置延迟间隔,营造更舒缓的编辑风格。
- 如果换行至关重要,需在字体加载完成后再初始化。
- 延迟加载图片或布局发生偏移后,调用。
ScrollTrigger.refresh() - 不要拆分包含链接、按钮或重要内联标记的文本。
Quick Checks
快速检查项
- Text is hidden before GSAP initializes, then becomes visible with .
autoAlpha: 1 - Screen readers get the original full text through .
aria-label - Spaces between words are preserved.
- Reduced-motion users see static text.
- ScrollTrigger is cleaned up in SPA routes.
- GSAP初始化前文本处于隐藏状态,随后通过变为可见。
autoAlpha: 1 - 屏幕阅读器通过获取原始完整文本。
aria-label - 文字间的空格得以保留。
- 启用减少动效的用户会看到静态文本。
- SPA路由中已清理ScrollTrigger。