masked-reveal について
GSAP と ScrollTrigger で、マスク越しに文字を順に表示するデザインスキルです。短い見出しやセクション導入向けに、初期化、アクセシビリティ、クリーンアップ、動きの既定値を扱います。
主な機能
- 01スクロール時にマスク越しで文字を表示
- 02GSAPによる単語ごとの段階表示
- 03空白とアクセシブルなテキストの保持
- 04動きを減らす設定とルートのクリーンアップ
活用シーン
- 01ランディングページの見出しをアニメーション化
- 02スクロールに合わせたセクション導入の表示
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mengto/ skills --skill masked-revealインストール後、Claude Code で/masked-revealを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
demo/index.htmlPROMPT.mdsource.json詳細
マスク付きリビール
使用する場面
- スクロール時に、見出しや短いテキストブロックを上質に表示したい場合。
- 見えないマスクを通って、単語が順番にせり上がるようにしたい場合。
- プロジェクトでGSAPをすでに使用している場合、またはScrollTriggerを使ったモーションが必要な場合。
モーションのデフォルト設定
- トリガー:テキストの上端がビューポートの
82%に達したときに開始します。 - デュレーション:
0.7s~0.9s。 - スタッガー:単語ごとに
0.025s~0.045s。 - オフセット:
yPercent: 110から0。 - イージング:
power3.outまたはexpo.out。 - リプレイ:デフォルトでは1回だけ表示します。
HTML
<h1 class="masked-reveal" data-masked-reveal>
Design systems that feel alive from the first scroll.
</h1>
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
このヘルパーを使うと、有料のSplitTextプラグインを避けつつ、スペースをそのまま維持できます。
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のクリーンアップパターン
useLayoutEffect(() => {
const ctx = gsap.context(() => {
initMaskedReveals("[data-masked-reveal]");
}, rootRef);
return () => ctx.revert();
}, []);
スタイルのルール
- 短い見出し、ラベル、セクションの導入文に使用し、長い段落は避けます。
- 縦方向のオフセットはすっきりと保ちます。スタイル上明確に必要な場合を除き、ぼかしと組み合わせないでください。
- 落ち着いたエディトリアルな雰囲気にするには、文字単位ではなく単語単位でスタッガーさせます。
- 行の折り返しが重要な場合は、フォントの読み込み後に初期化します。
- 画像の読み込みが遅れた場合やレイアウトが変化した場合は、
ScrollTrigger.refresh()を使用します。 - リンク、ボタン、または意味のあるインラインマークアップを含むテキストは分割しないでください。
簡易チェック
- GSAPの初期化前はテキストが非表示になり、その後
autoAlpha: 1で表示されます。 - スクリーンリーダーには、
aria-labelを通じて元の全文が伝わります。 - 単語間のスペースが維持されます。
- 視差効果を減らす設定のユーザーには、静的なテキストが表示されます。
- SPAのルート切り替え時にScrollTriggerがクリーンアップされます。
コメント
Markdown 対応