关于 staggered-word-reveal
用于文字进入视口时逐词显现的前端设计技能,提供克制且仅播放一次的动画实现指导,并考虑无障碍与减少动态效果设置。
主要功能
- 01逐词拆分与显现
- 02进入视口时触发一次
- 03CSS 与 JavaScript 实现
- 04屏幕阅读器与减少动态效果指导
适用场景
- 01滚动时动画呈现短标题
- 02逐词显示引言
- 03为标签添加克制动效
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mengto/ skills --skill staggered-word-reveal安装后在 Claude Code 中输入/staggered-word-reveal确认已加载。
附带文件demo/index.htmldemo/PROMPT.md
详细介绍
错落式逐词显现
适用场景
- 短标题、引言或引用语需要逐词显现。
- 动效应显得有编辑感、高级且克制。
- 文本进入视口时,动效只触发一次。
- 项目不需要 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 += 1;
});
element.dataset.wordRevealReady = "true";
element.classList.add("is-ready");
}
function initWordReveals(selector = "[data-word-reveal]") {
const elements = Array.from(document.querySelectorAll(selector));
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduceMotion || !("IntersectionObserver" in window)) {
elements.forEach((element) => {
element.classList.add("is-ready", "is-visible");
});
return;
}
const observer = new IntersectionObserver(
(entries, io) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add("is-visible");
io.unobserve(entry.target);
});
},
{
threshold: 0.2,
rootMargin: "0px 0px -10% 0px",
}
);
elements.forEach((element) => {
splitWordReveal(element);
observer.observe(element);
});
}
document.addEventListener("DOMContentLoaded", () => {
initWordReveals();
});
框架说明
- React/Vue/Svelte:在挂载后运行拆分器,并在路由更改时清理观察器实例。
- Framer Motion:保持相同参数:
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添加动画。 - 不要拆分包含链接、按钮或有意义的行内标记的文本。
- 如果换行效果很重要,请在网页字体加载完成后初始化。
快速检查
- 禁用 JavaScript 时,文本仍然可见。
- 各词从
translateY(20px)和opacity: 0状态开始。 - 每个词仅显现一次,间隔为
0.06s到0.08s。 - 重复滚动不会重播动画。
- 减少动态效果的用户会看到静态且易读的文本。
评论
支持 Markdown