scroll-and-webgl について
マーケティングページのスクロール演出、WebGL、Canvas シーンを扱うデザインスキルで、性能とアクセシビリティを重視した予算設定と実装方法を示します。スクリプト実行前のテキスト表示や、動きを減らす設定および JavaScript 無効時の代替表示を保ちながら、表現豊かなページを作る制作者向けです。
主な機能
- 01複雑さに応じたスクロールアニメーション手法
- 02スクロール演出の性能予算
- 03テキスト表示とアクセシビリティの指針
- 04Canvas と WebGL のリソース管理
- 05遅延読み込みと読み込み順序
- 06動きを減らす設定と JavaScript 無効時の代替表示
活用シーン
- 01マーケティングページへのスクロール表示効果の追加
- 02固定またはスクロール連動シーンの構築
- 03静止画フォールバック付き WebGL ヒーローの作成
- 04JavaScript 無効時にもページ内容を表示
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ paragonhq/ stealth-skills --skill scroll-and-webglインストール後、Claude Code で/scroll-and-webglを入力して読み込まれたか確認します。
詳細
scroll-and-webgl
動きを主役にできるのは、この階層だけです。失敗すると、作者のノートパソコンでは美しくても、ほかの環境では使いものにならないページになるため、ルールは緩くなるのではなく、より厳しくなります。
適用範囲
| 画面 | 許可 |
|---|---|
| マーケティングページ、ローンチ、ポートフォリオ、事例紹介 | 以下の予算を守れば可 |
| ドキュメント、ブログ、読書 | タイトルの表示演出まで |
| プロダクト UI、ダッシュボード、ツール、設定 | 不可。代わりに motion スキルを読み込んでください |
プロダクト画面にスクロールエフェクトを使う案が出た場合、答えは「不可」です。その理由は、プロダクト画面は何百回も使われるからです。
予算
開始前に設定し、最後に確認します。
- CPU を 4× にスロットルした状態で、スクロール全体を 60fps に保つこと。1 秒に 1 フレームでも落ちたら不合格です。
- エフェクト適用時の LCP を 2.5s 未満にすること。ヒーローのテキストは HTML に含め、スクリプトの実行前から表示しておきます。
- INP を 200ms 未満にすること:スクロールハンドラーではレイアウトを読み取らず、すべてをコンポジター上で処理するか、
requestAnimationFrameのバッチ内で処理します。 - メモリ:ページあたりの WebGL コンテキストは 1 つ。テクスチャは 2048px 未満にします。アンマウント時に破棄します。
- 1 つのビューポートにつき、見せ場は 1 つ。ヒーローのシェーダー、ピン留めされたスクロールシーン、分割テキストの表示演出のいずれか 1 つにします。同時に 2 つ使うと、ただのノイズになります。
コストの低い順に並べたスクロール連動の手法
1. CSS スクロールドリブンアニメーション
JavaScript は不要で、コンポジター上で動作し、メディアクエリ 1 つで視差効果を抑えたモーションに対応できます。
.reveal {
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 40%;
}
@keyframes fade-up { from { opacity: 0; transform: translateY(24px); } }
用途:要素の表示、進捗バー、10% 未満の視差効果、縮小するスティッキーヘッダー。非対応ブラウザーでは「そのまま表示」にフォールバックします。これが適切なフォールバックです。
2. IntersectionObserver とクラス
CSS のタイムラインに対応していない場合や、ライブラリのエントランス演出を使いたい場合の、一度だけ表示するエフェクトに使います。監視して、クラスを追加し、監視を解除します。しきい値は 0.15~0.3、要素が画面下端に達する少し前に表示されるよう、rootMargin: "0px 0px -10% 0px" にします。
3. GSAP ScrollTrigger
スクロールに同期する、ピン留めする、または連続して演出するものに使います。
scrub: true(または少し遅れを加える0.5~1)で、タイムラインをスクロールバーに連動させます。これ以外に、同じくらい滑らかな方法はありません。pin: trueを使うと、シーンの再生中にセクションを固定できます。ピン留めするセクションのスクロール量は 300vh 未満にしてください。そうしないと、ユーザーが迷ってしまいます。- ピン留めでは
anticipatePin: 1を指定し、1 フレームのジャンプを防ぎます。 ScrollTriggerは要素ごとではなく、シーンごとに 1 つ使います。要素の連続した演出は、1 つのタイムライン内にまとめます。matchMediaを使い、768px 未満の画面幅と視差効果を抑えたモーションが有効な場合は、ピン留めとスクロール同期を無効にします。- フォントの読み込み後、およびレイアウトの変更後に
ScrollTrigger.refresh()を実行します。
4. Lenis(スムーズスクロール)
Lenis はホイール入力を補間することで、スクロール連動のアニメーションを滑らかにします。マーケティングページには適していますが、それ以外には適しません。
- 視差効果を抑えたモーションが有効な場合は無効にします。タッチ操作時も無効にします(ネイティブの慣性スクロールのほうが優れています)。独自のスクロールバーがある領域でも無効にします(
data-lenis-prevent)。 - Duration は 1.1~1.3、
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))にします。 - Lenis から GSAP の ticker を駆動します(
lenis.on("scroll", ScrollTrigger.update))。これにより、両者の動きを同期させます。 - アンカーへの移動にはネイティブの
scrollIntoViewではなく、lenis.scrollTo(el, { offset: -header })を使います。 - モーダルやメニューを開いている間は、
lenis.stop()を呼び出します。
テキストの表示演出
短いテキスト(1 語のロゴなど)を除き、文字単位ではなく、行または単語単位で分割します。文字単位ではタイプライター風になり、すぐに古く感じられます。
- 行:
overflow: hiddenのラッパー内でtranslateY(100%)を適用し、80ms のスタガー、700ms の ease-out-expo にします。定番で、今でも最良の手法です。 - 単語:不透明度 +
translateY(6px)+blur(6px→0)を適用し、30ms のスタガーにします。 - 表示するのは 1 回だけです。最初に表示された後は、テキストをそのままにします。
- 親要素に完全な文字列を含む
aria-labelを設定し、分割した span にはaria-hiddenを指定します。JavaScript がなくても、テキストは通常どおり表示されます。 - リサイズ時に再分割します。ある幅で分割したテキストは、別の幅では改行が崩れます。GSAP SplitText はこれに対応します。手動で分割する場合は、リサイズを監視する必要があります。
Canvas と WebGL
用途
シェーダーの背景、ポインターに反応する 3D オブジェクト、パーティクルのフィールド、プロダクトモデルなどに使います。CSS グラデーションとノイズ SVG で実現できるなら、そちらを使ってください。
方法
- デバイスピクセル比の上限は2とし、大きなキャンバスでは1.5にします。3×のRetinaは、目に見える効果がないのにピクセル数が4倍になります。
- IntersectionObserverと
document.hiddenを使って、画面外では一時停止します。キャンバスが非表示のままGPUを使い続けることは、ノートパソコンが熱くなる最もよくある原因です。 ResizeObserverでサイズを変更し、デバウンス処理を入れます。描画バッファのリセットはフレームごとではなく、一度だけ行います。- ページで使うアニメーションループは1つにします。複数の
requestAnimationFrameループは互いに競合します。 - ライブラリとシーンは遅延インポートします。まずヒーローのテキストを表示し、準備ができたら400ms後にキャンバスをフェードインします。three.jsの読み込みを待つためにヒーローが空白になるのは失敗です。
- アンマウント時にジオメトリ、マテリアル、テクスチャ、レンダラーを破棄します。SPAでは、ルート変更をまたぐメモリーリークがよく起こります。
- ポインターへの反応にはイージングを適用します。フレームごとにターゲットを0.08〜0.12ずつ補間します。1対1で直接追従させると機械的に感じられます。
- フォールバック:WebGLが使えない場合、動きを抑える設定が有効な場合、ローエンドデバイスの場合に、同じ色と強さの静止画像またはCSSグラデーションを使います(大まかな判定条件として、
navigator.hardwareConcurrency < 4とdeviceMemory < 4を確認します)。
マテリアルとしてのシェーダー
背景には、3Dシーンよりもフラグメントシェーダーのほうが低コストで、より適していることもよくあります。ゆっくり変化するグラデーションノイズ、ディザリングしたグロー、グレインなどを使います。30行未満に抑え、時間とポインターユニフォームでアニメーションさせ、上に載せるテキストを読みやすくするためにコントラストを低く保ちます。ディザリング(規則的なパターンまたはブルーノイズ)は、暗いグラデーションのバンディングを目立たなくし、質感を加えます。
3D
Three.jsまたはReact Three Fiberを使います。モデルは圧縮したglTF(DracoまたはMeshopt)形式にして2MB未満に抑え、最終的なシルエットに合う<Suspense>フォールバックを用意します。環境光には小さなHDRIを使います。影が演出の要でない限り、影はオフにします。ポストプロセスは1パス、またはなしにします。
読み込み戦略
- すべてのテキストを含むHTMLと、寸法を指定したファーストビューの画像を用意します。初回描画時に表示されます。
- フォント(2種類のウェイト、プリロード済み)。
- レイアウトが安定します。この時点でLCPが記録されます。
- スクロールライブラリとオブザーバー。
- キャンバス、WebGL、モデル:遅延読み込みし、アイドル後にフェードインします。
ステップ5が失敗またはスキップされても、ページは完成している状態にします。それがテストです。
動きを抑える設定とJavaScriptなしの場合
prefers-reduced-motion: reduceの場合:
- Lenisなし。ピン留めなし。スクラブなし。キャンバスの動きなし(静止フレームは問題ありません)。
- リビールは実行せず、コンテンツを表示します。
- 動画は自動再生しません。
JavaScriptがない場合:
- リビール用に非表示にしたものはすべて表示されます(
html:not(.js) [data-reveal] { opacity: 1 })。 - シーンがあるはずの箇所に空白を作らず、ページを上から下へ読めるようにします。
ページを閉じられてしまう兆候
| 兆候 | 対策 |
|---|---|
| スクロールが乗っ取られ、ホイール操作に違和感がある | マーケティングページだけでLenisを使い、タッチ操作では無効にするか、使わない |
| ピン留めされたセクションが300vhを超える | シーンを短くし、ピン留めを減らす |
| スクリプトで分割した後にしか存在しないテキスト | テキストはHTMLに置き、分割は拡張として使う |
| 画面外でもキャンバスが動作している | IntersectionObserverで一時停止する |
| 全画面キャンバスでDPRが3 | 上限を2にする |
| three.jsの読み込みが終わるまでヒーローが空白 | まずテキストを表示し、キャンバスをフェードインさせる |
| パララックスが30% | 10%未満にするか、使わない |
| 段落内の文字を1文字ずつリビールする | 行単位にする |
| ダッシュボードでエフェクトを使う | 削除する |
カタログから
- GSAPとScrollTrigger、SplitText — これらのレシピで想定しているタイムラインツールです。
- Lenis — 上記の注意点を踏まえたスムーズスクロールです。
- Three.js、React Three Fiber — Web上の3Dです。
- Codrops — スクロール、WebGL、トランジションに関する実験的なチュートリアルです。
- Maxime Heckel — シェーダーやWebGLを、実際に制作・公開している人が解説しています。
- Godly、Awwwards — 効果をコピーする前に、その効果が何のためにあるのかを調べましょう。
- その他はstealth.pm/buildをご覧ください。