mask-reveal-transition について
有機的なマスクで画像を見せる、HTML・CSS・JavaScriptのデザインスキルです。スクロール進行に応じる方式と、固定分割レイアウトでパネルに合わせて画像を切り替える方式を扱い、動きを抑える設定やブラウザー検証も含みます。
主な機能
- 01有機的な放射状マスクによる画像表示
- 02スクロール進行に連動するマスク半径
- 03固定分割レイアウトでの画像切り替え
- 04動きを抑える設定への対応
- 05Safari向けマスク接頭辞の維持
活用シーン
- 01ポートフォリオにインク状の画像表示を追加
- 02事例テキストに合わせ固定画像を切り替え
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rheinmir/ setup --skill mask-reveal-transitionインストール後、Claude Code で/mask-reveal-transitionを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/demo.htmlmask-reveal-transition.cssmask-reveal-transition.js詳細
スキル:mask-reveal-transition
バニラ HTML/CSS/JS コンポーネント — 通常のクロスフェード/不透明度ではなく、有機的な形のマスク(インクがにじむ輪、固定された中心、徐々に広がる半径)を通して画像を表示します。2 つのバリエーションは共通の仕組みを使用します。getBoundingClientRect() でセクション内のスクロール進行度を測定して 0..1 にマッピングし、にじみの半径を CSS カスタムプロパティに書き込みます。
WHAT
目的とコンテキスト
- 目的: スクロール進行度に合わせて有機的な形のマスク(インクがにじむ輪)を通して画像を表示/切り替えるか、固定された 2 カラムのスプリットスクリーンで、読んでいるパネルに合わせてマスク越しに画像を切り替えます —
Rheinmir/uiux-assetの元コードをそのまま優先し、assets/は軽量なバニラ版の予備として使います。 - トリガー(使用するケース):
- スクロールに同期して「インクのにじみ」のように画像をアーティスティックに切り替える場合(単純なクロスフェードではありません)。
- ケーススタディ/ポートフォリオの 2 カラムレイアウト:左側に固定画像、右側にスクロールする文章を配置し、読んでいる段落に合わせて画像を切り替える場合。
- 対象外: シンプルな表示演出(ビューポートに入ったときにフェード+少しスライド)には使用しません — これは
skills/scroll-effectsパターン #1 で、形状マスクはありません。GPU 負荷の高いズームブラーによる全画面画像切り替えには使用しません — これはskills/blur(Three.js/シェーダー)です。このスキルで使うのは CSS のmask-imageのみで、canvas/WebGL は使用しません。
メンタルモデル
section → スクロール進行度を測定(getBoundingClientRect → 0..1)またはアクティブなパネルを検出(IntersectionObserver)→ --mrt-r →「上」の画像に radial-gradient の mask-image を適用 → 透明な穴がにじみながら広がる →「下」の画像が見える。2 つのバリエーション:[data-mrt-blob](各フレームで --mrt-r を書き込み、transition なし)と [data-mrt-split](パネルに応じて段階的に切り替え、@property による transition あり)。
入出力の契約
| フィールド | 必須? | 意味 | |
|---|---|---|---|
| 入力 | 画像(blob は 2 枚、split は各パネルに 1 枚) | はい | 元ファイル/アセットの内容と置き換えるもの |
| 入力 | プロジェクトで外部ライブラリ/ファイルを読み込めるか | いいえ(デフォルト:可能) | 不可の場合は assets/ のバニラ版を使用 |
| 出力 | マスク表示効果を備えたページ/コンポーネントの HTML | はい | ローカル HTTP 経由で動作し、-webkit-mask-image と prefers-reduced-motion の分岐を含む |
| 出力 | playwright の検証結果 | はい | 変化前/途中/後のスクリーンショット(バリエーション 1)、画像切り替えと固定表示のアサーション(バリエーション 2) |
ルールと機能
- RULE-01 (MUST): ブラウザーの
mask-image対応: Safari(最新の Safari を含む)は-webkit-mask-image/-webkit-mask-repeatのみを認識します — 標準のmask-imageと-webkit-mask-imageの両方を必ず記述してください(アセットの CSS にすでに含まれているため、適用/圧縮時にプレフィックスを削除しないでください)。プレフィックスがないと、Safari では上の画像に穴が開かず、効果が動作しません。 - RULE-02 (MUST): いずれかの祖先要素(遠い祖先や
<body>まで含む)にoverflow: hidden/auto/scrollまたはclipがあると、position: stickyは動作しません — 既存のレイアウトに.mrt-split-stickyを埋め込むときに最もよく起きる問題です(多くのページでは横スクロール防止のためにラッパーにoverflow: hiddenを設定しており、意図せずすべての子要素の sticky を無効にしています)。「sticky が動かない」ことをバグとして報告する前に、すべての祖先要素を確認してください。 - RULE-03 (MUST): バリエーション 2 では、ブラウザーが
calc()/グラデーション内のカスタムプロパティをアニメーションできるように、@property --mrt-rを使用します。この宣言がないと、JS で--mrt-rを変更したとき、transition でなめらかに変化せず瞬時に切り替わります。 - RULE-04 (MUST):
prefers-reduced-motion: reduceの場合 → 両方のバリエーションで--mrt-rを最大半径に即座に設定し(画像を完全に表示)、アニメーション/transition を無効にします — JS/CSS に実装済みのため、適用時にこの分岐を削除しないでください。 - RULE-05 (MUST): ページ上のすべての
[data-mrt-blob]に対して、rAF でスロットリングしたスクロールリスナーを 1 つだけ使用します。各[data-mrt-split]のパネルには、IntersectionObserverを 1 つだけ使用します。個別のscrollリスナーを複数登録したり、同じ役割の observer を重複して登録したりしないでください。 - RULE-06 (MUST):
playwright-verifyで検証してください。バリエーション 1 ではスクロール中の前/途中/後を撮影し、にじみの輪が徐々に大きくなることを確認します。バリエーション 2 では、各パネルがビューポート中央に入ったときに正しい画像へ切り替わること、および読んでいる間、画像カラムが実際に固定され(ページと一緒にスクロールしない)ていることをアサートします。 - 機能:外部アセットリポジトリまたはスキルの
assets/からファイルを読み込み/コピーすること、プロジェクトの HTML/CSS/JS ファイルを書き込むこと、ヘッドレスブラウザーを実行して検証することができます。
失敗時の境界
- Safari でエフェクトが動作しない →
-webkit-mask-imageが不足しています → prefix を修正し、別のバグだと結論づけないでください。 - Sticky が固定されない → バグを報告する前に、すべての親要素をたどって
overflow/clipを確認してください。 file://で開いている → 無効です。HTTP 経由で再実行してください。- 実際の要件が opacity/translate の reveal、または全画面の shader 画像切り替え → このスキルの対象外です。
skills/scroll-effectsまたはskills/blurに引き継いでください。
方法
主なワークフロー
| ステップ | 種別 | 入力 | アクション | 出力/終了条件 | 失敗時/次の手順 |
|---|---|---|---|---|---|
| W01 | 判断 | 要件 | バリエーション(blob または split)とソースを選択します。オリジナル(デフォルト)または assets/ の vanilla 版です | 選択 | 外部ライブラリを導入できない場合 → B01 |
| W02 | エフェクト | オリジナルの pen | 実行リンクを開いてエフェクトを確認 → .html ファイル全体(および _assets/)をコピー → クレジットコメントを保持 → 内容/画像/色のみを変更 | プロジェクト内のファイル | — |
| W03 | 決定論的 | ページ | HTTP 経由でローカル実行し、playwright-verify で検証します | スクリーンショット + アサーション | Safari/sticky のエラー → 失敗時の境界を確認して修正し、再実行 |
各ステップの詳細(W01〜W03 の信頼できる情報源):
オリジナル — まずはこちらを使用(ファイル全体をコピーし、書き直さない)
作者によるオリジナルのコードそのまま(公開 CodePen = MIT)は、外部リポジトリ Rheinmir/uiux-asset にあります — 実際に動作するギャラリー:https://rheinmir.github.io/uiux-asset/scroll-effects/ 。
手順:実行リンクを開いてエフェクトを確認 → .html ファイル全体(ファイルから参照されている場合は _assets/ フォルダーも)をプロジェクトにコピー → 冒頭行のクレジットコメントを保持 → 内容/画像/色のみを変更します。ローカルでは HTTP 経由で実行し、file:// は使わないでください。
オフラインで一式が必要な場合は、git clone --depth 1 https://github.com/Rheinmir/uiux-asset を実行してください。
| オリジナルの pen — 作者 | リンク |
|---|---|
| Ink Transition Scroll Effect — iamryanyu | 実行 · コード |
| Pinned Split-Screen Mask Reveal — gridmorphic | 実行 · コード |
| Parallax Jungle Leaves Reveal — Mamboleoo | 実行 · コード |
このスキルの assets/ は、独自に作成した vanilla 版の簡易実装にすぎません。外部ライブラリを導入できないプロジェクトでのみ使用してください。デフォルトでは、上記のオリジナルを使用します。
アセット
assets/demo.html + assets/mask-reveal-transition.css + assets/mask-reveal-transition.js — 内容を確認してから ADAPT します(画像、--mrt-edge の境界の柔らかさ、パネルの内容を変更)。
分岐
| ID | 種別 | 条件 | 動作 | スキップ/失敗 | 再開先 |
|---|---|---|---|---|---|
| B01 | 必須条件 | プロジェクトで外部ライブラリ/ファイルを導入できない | assets/demo.html + .css + .js を使い、内容を確認してから ADAPT します(画像、--mrt-edge、パネルの内容) | — | W03 |
| B02 | ユーザー任意 | オフラインで一式が必要 | git clone --depth 1 https://github.com/Rheinmir/uiux-asset | — | W02 |
検証と完了条件
決定論的(playwright):バリエーション 1 では、スクロール前/途中/後のスクリーンショットを撮影し、にじみの円が大きくなることを確認します。バリエーション 2 では、各パネルがビューポート中央に入ったときに正しい画像へ切り替わること、および画像列が実際に固定されることをアサートします。要確認:「インクのにじみ」の方向が適切であること(徐々に現れ、徐々に隠れるのではないこと)。上記のアサーションがすべて成功したら終了します。
例
- 適切な例: 2 列のポートフォリオ事例紹介で「左に画像を固定し、右のテキストをスクロール」する →
Rheinmir/uiux-assetのpinned-split-screen-mask-reveal.html全体をコピーし、パネル画像を変更 → playwright で、各[data-mrt-panel]がビューポート中央に入ると画像が切り替わり、画像列がページと一緒にスクロールしないことをアサートします。 - 境界/失敗の例:
.mrt-split-stickyをoverflow: hiddenのラッパーがあるレイアウトに埋め込む → 画像がページと一緒にスクロールし、固定されない → 親要素をたどって確認し、ラッパーのoverflowを削除します。コンポーネントのバグとして報告しないでください。minify によって-webkit-mask-imageが削除される → Safari で mask の穴が表示されない → prefix を戻します。
リファレンス — 共通の基盤メカニズム
各セクション([data-mrt-blob] または [data-mrt-split])では、同じフレーム内に2枚の画像を絶対配置で重ねます。下の画像(z-index が低く、常に表示)と、マスクを持つ上の画像(z-index が高い)です。上の画像には
mask-image: radial-gradient(circle at 50% 50%, transparent 0, transparent var(--mrt-r), black calc(var(--mrt-r) + var(--mrt-edge)), black 100%);
があります。半径 --mrt-r の内側は透明(上の画像がその範囲で消え、下の画像が見える)で、--mrt-r + --mrt-edge の外側は不透明(上の画像が完全に覆い隠す)です。--mrt-r が徐々に大きくなると、透明な円が広がり、下の画像が中心から「にじみ出る」ため、インクの染みのような感覚になります。逆方向(不透明な点が大きくなる)ではありません。そうすると、徐々に覆い隠すことになるためです。
バリエーション 1 — Blob/インクマスクによる表示([data-mrt-blob])
<section data-mrt-blob>
<div class="mrt-blob-frame">
<img class="mrt-blob-under" src="B.jpg" alt="…">
<img class="mrt-blob-over" src="A.jpg" alt="…">
</div>
</section>
スクロールの進行状況に応じて、--mrt-r を各フレームで直接設定します(CSS トランジションは使いません)。スクロールリスナーを rAF でスロットルし、stage.getBoundingClientRect() を計測します。progress = (vh - rect.top) / (vh + rect.height) を 0~1 の範囲に収め、フレームの最大半径(対角線の半分 + マージン)を掛けて、over.style.setProperty('--mrt-r', ...) を実行します。このバリエーションではトランジションを使いません。トランジションを使うと、実際のスクロール位置に対して円の広がりが遅れるためです(svg-stroke-reveal の「scroll」モードでトランジションを使わないのと同じ理由です)。
バリエーション 2 — ピン留め式スプリットスクリーンマスクによる表示([data-mrt-split])
<div class="mrt-split-section" data-mrt-split>
<div class="mrt-split-sticky">
<img class="mrt-split-under" alt="…">
<img class="mrt-split-over" alt="">
</div>
<div class="mrt-split-scroll">
<article data-mrt-panel data-mrt-src="1.jpg">…</article>
<article data-mrt-panel data-mrt-src="2.jpg">…</article>
</div>
</div>
画像列には、純粋な CSS の position: sticky を使います(GSAP/ScrollTrigger は使いません。ブラウザー標準の機能で最も低コストにピン留めします)。JS の役割は、アクティブなパネルに応じて画像/マスクを切り替えることだけです。各 [data-mrt-panel] に IntersectionObserver(threshold 0.5)を設定し、パネルがビューポートの中央に入ると revealTo(src) を呼び出します。現在の画像を「上」のレイヤーに移動して完全に覆い隠し(--mrt-r = 0 を即時設定して、リフローを強制)、新しい画像を「下」のレイヤーに設定した後、requestAnimationFrame で --mrt-r を最大半径に設定します。これにより、@property --mrt-r { syntax: "<length>"; ... } で登録された CSS トランジションが滑らかに実行されます。ここでは --mrt-r はスクロールのピクセル単位ではなく、パネルのイベントごとに離散的に変わるため、バリエーション 1 とは異なり、--mrt-r に CSS トランジションを使うのが適切です。
オリジン
Batch 180926 — 元のソース全文は Rheinmir/uiux-asset/scroll-effects/ にあります(上記の「元のソース」セクションを参照)。assets/ は、自作の vanilla 版に簡略化したものです。