| name | staggered-word-reveal |
| description | 创建含蓄的编辑级逐词文本显现动画——每个词在进入视口后依次淡入并上浮到位。当高端作品集标题、主视觉文案、章节引言以及需要电影感错峰显现的短营销文本要用 IntersectionObserver 或 in-view 检测时使用。 |
逐词错峰显现
何时使用
- 一段短标题、引言或摘录金句需要逐词显现。
- 动效应显得有编辑感、高端、克制。
- 显现应仅在文本进入视口时触发一次。
- 项目不需要 GSAP SplitText 那样重的行为。
动效默认值
- 初始状态:
opacity: 0、transform: translateY(20px)。
- 结束状态:
opacity: 1、transform: translateY(0)。
- 时长:
0.8s。
- 缓动:
cubic-bezier(0.16, 1, 0.3, 1)。
- 错峰:每词
0.06s 到 0.08s。默认取 0.07s。
- 触发:约
20% 可见时开始,并略偏向视口下方。
- 重播:仅一次。
HTML
<h1 class="word-reveal" data-word-reveal>
Build interfaces that feel calm, cinematic, and alive.
</h1>
CSS
保持无 JS 时内容可见。仅在 JavaScript 生效后、文本被拆分前才隐藏。
.word-reveal {
visibility: visible;
}
html.js .word-reveal[data-word-reveal]:not(.is-ready) {
opacity: 0;
}
.word-reveal__word {
display: inline-block;
opacity: 0;
transform: translate3d(0, 20px, 0);
transition:
opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
transition-delay: calc(var(--word-index) * 0.07s);
will-change: opacity, transform;
}
.word-reveal.is-visible .word-reveal__word {
opacity: 1;
transform: translate3d(0, 0, 0);
}
@media (prefers-reduced-motion: reduce) {
html.js .word-reveal[data-word-reveal]:not(.is-ready),
.word-reveal__word {
opacity: 1;
transform: none;
transition: none;
}
}
JavaScript
这个拆分器会保留空格、避免使用 innerHTML、把原始句子暴露给屏幕阅读器,并在首次显现后取消观察。
document.documentElement.classList.add("js");
function splitWordReveal(element) {
if (element.dataset.wordRevealReady === "true") return;
const text = element.textContent || "";
const parts = text.split(/(\s+)/);
let wordIndex = 0;
element.textContent = "";
element.setAttribute("aria-label", text.trim());
parts.forEach((part) => {
if (!part.trim()) {
element.appendChild(document.createTextNode(part));
return;
}
const word = document.createElement("span");
word.className = "word-reveal__word";
word.setAttribute("aria-hidden", "true");
word.style.setProperty("--word-index", wordIndex);
word.textContent = part;
element.appendChild(word);
wordIndex += ;
});
element.. = ;
element..();
}
() {
elements = .(.(selector));
reduceMotion = .().;
(reduceMotion || !( )) {
elements.( {
element..(, );
});
;
}
observer = (
{
entries.( {
(!entry.) ;
entry...();
io.(entry.);
});
},
{
: ,
: ,
}
);
elements.( {
(element);
observer.(element);
});
}
.(, {
();
});
框架说明
- React/Vue/Svelte:在挂载后运行拆分器,并在路由切换时清理 observer 实例。
- Framer Motion:沿用相同 token:
y: 20、opacity: 0、时长 0.8、缓动 [0.16, 1, 0.3, 1]、错峰 0.06 到 0.08、once: true。
- GSAP:使用
fromTo(words, { y: 20, opacity: 0 }, { y: 0, opacity: 1, duration: 0.8, ease: "expo.out", stagger: 0.07 })。
审美准则
- 用在短文本上:标题、副标题、标签和金句。避免长段落。
- 错峰以词为单位而非字母,以获得更沉静高端的感觉。
- 保持位移含蓄。不要加入弹跳、旋转或大幅模糊。
- 仅动画化
transform 和 opacity。
- 不要拆分含链接、按钮或有意义行内标记的文本。
- 若换行很重要,请在 web 字体就绪后再初始化。
快速检查
- JavaScript 被禁用时文本可见。
- 词从
translateY(20px) 和 opacity: 0 开始。
- 每个词以
0.06s 到 0.08s 的延迟显现一次。
- 反复滚动不会重播动画。
- 减弱动效的用户看到静态可读的文本。