mcpField
I
infinite-webgl-grid

infinite-webgl-grid

github.com/rheinmirデザイン・UI1075 回インストール2 週間前に更新
リポジトリ

infinite-webgl-grid について

infinite-webgl-gridは、ドラッグやスクロールで全方向に閲覧できる画像グリッドを作るThree.jsスキルです。メッシュを生成・破棄し続ける代わりに、座標ラッピングで固定数のメッシュとテクスチャを再利用します。

主な機能

  1. 01ドラッグやスクロールで閲覧
  2. 02座標ラッピングによる無限グリッド
  3. 03固定メッシュとテクスチャの再利用
  4. 04グリッドとタイルの設定
  5. 05オフライン用プレースホルダー表示

活用シーン

  1. 01ポートフォリオ用のドラッグ可能な画像壁
  2. 02全方向に閲覧できるポスター壁
  3. 03連続して巡れる画像ギャラリー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rheinmir/setup --skill infinite-webgl-grid

インストール後、Claude Code で/infinite-webgl-gridを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
demo.css
demo.html
demo.js

詳細

スキル:infinite-webgl-grid

純粋な Three.js コンポーネント — 固定された画像タイルの N×N グリッドを、座標ラッピング(オフセットの剰余演算)を使って、あらゆる方向に無限に広がるようにドラッグ(ポインター)またはスクロール(ホイール)で閲覧できます。メッシュを継続的に生成・破棄することはありません。

WHAT

目的と背景
  • 目的: 座標ラッピングを使って、あらゆる方向に無限に閲覧できる WebGL 画像グリッド N×N を構築します。Rheinmir/uiux-asset のオリジナルをそのままコピーすることを優先します。
  • 使用するタイミング:
    • あらゆる方向に「無限に閲覧」できる感覚が必要なポートフォリオ/ギャラリー/ポスターウォール(一般的な一方向スクロールではありません)。
    • ユーザーが「無限画像グリッド」「infinite scroll grid webgl」「draggable image wall」「無限スクロールのポスターウォール」と言った場合。
  • 対象外:
    • スクロールの進行に合わせてパーティクルを特定の1枚の画像に集約する必要がある場合は使用しないでください(これは threejs-particle-morph です。「到達済み」の状態があるのに対し、こちらには目的地がなく、連続的に閲覧するだけです)。
    • 1つのフレーム上で2枚の画像を切り替えるエフェクトには使用しないでください(これは blur です)。
メンタルモデル

仮想オフセット(ドラッグ/スクロール/慣性)→ 各固定メッシュ:wrap(localX - offset.x) → 表示位置 → 仮想タイルのインデックスが変化 → 固定テクスチャプールからmaterial.mapを変更。カメラは固定され、実行中にメッシュを追加・削除しません(詳細は「Reference — 仕組み」を参照してください)。

入出力の仕様
項目必須?意味
入力画像/コンテンツ+色いいえ不足時はデモがプレースホルダーの Canvas を自動描画し、オフラインで動作します
入力プロジェクトの制約:外部ライブラリ/ファイルを取り込めるかはい可能ならオリジナル版、不可能なら assets/ の vanilla 版
入力gridSize/tileSize/gap/poolSizeいいえInfiniteGrid の初期化パラメーター
出力.html ファイル(+_assets/)またはプロジェクト内で適応済みの demo.html/.css/.jsはいオリジナル版の場合は、先頭行にクレジットコメントを残します
出力Playwright による検証の証拠はい4方向にドラッグする前/途中/後の画像+エラーのないコンソール
ルールと機能
  • RULE-01 (MUST): 同時に使用するタイル/テクスチャ数の制限:モバイル GPU では、同時にアクティブにできるテクスチャユニット数に制限があります。実際に表示されるグリッド(ラップ用のバッファを除く)は、同時に 5×5 タイル以下を目安にしてください。デモの既定値は gridSize=7(表示タイル5枚+各辺にバッファ1枚で、タイルが滑らかに飛び移り、ポップインを防ぎます)です。表示タイル数より多くの異なる画像が必要な場合は、poolSize を無制限に増やすのではなく、テクスチャアトラス(複数の画像を1つのテクスチャにまとめ、個別のテクスチャごとにmaterial.mapを変更する代わりにUVオフセットを変更する方法)を使用してください。個別のテクスチャは、同時に表示されなくても、コードが参照を保持している限りそれぞれ1つのテクスチャユニットを消費します。
  • RULE-02 (MUST): Passive: false は必須:wheelとpointermoveのリスナーは{ passive: false }を指定して登録し、適切な場所(ドラッグ中、またはキャンバス内にいる場合のみ)でpreventDefault()を呼び出す必要があります。このフラグがないと、最新のブラウザーでは既定で passive になり、preventDefault()がひそかに無視されます(エラーも警告もありません)。その結果、ページ背景もグリッドと同時にスクロール/ズームし、モバイルでカクつきや競合が発生します。コンテナに対する CSS touch-action: none は必要な条件ですが、preventDefaultの代わりにはなりません。
  • RULE-03 (SHOULD): デモには直交カメラを使用してください(グリッドが平面で、遠近法による歪みがありません)。奥行きのエフェクト(遠くのタイルがぼやける/小さくなる)が必要ならPerspectiveCameraに変更してください。その場合も wrap の式は変わらず、world-unit をピクセルに変換する方法だけが変わります。
  • RULE-04 (MUST): playwright-verifyで検証してください — 強くドラッグしている最中の前/途中/後を撮影し(4方向すべてをテスト)、コンソールを確認します(WebGL のコンテキストエラーはキャンバス上では気づきにくく、明確な JS エラーを投げないことがあります)。
  • 機能:外部アセットリポジトリ(またはスキルのassets/)からファイルを読み取り/コピーする、プロジェクトにファイルを書き込む、ローカル HTTP 経由で配信する、ヘッドレスブラウザーを操作して撮影しコンソールを読み取る。
失敗時の境界
  • プロジェクトで外部ライブラリを取り込めない場合 → partial は許容されます。オリジナル版の代わりに、簡易版のassets/を使用します。
  • file://経由で開いた場合 → テクスチャ/アセットが失敗します → HTTP 経由で実行するまでblockedです。
  • コンソールに WebGL エラーがある場合、またはページ背景がグリッドと同時にスクロールする場合 → 検証はfailedです。修正(passive:false + touch-action: none)してから再検証してください。
  • 実際の要件がパーティクルの集約/2枚の画像の切り替えである場合 → clarifyし、threejs-particle-morph/blurに切り替えます。

方法

主なワークフロー
ステップ種類入力アクション出力/終了失敗時/次のステップ
W01判断要件スキルが適切か(無限に閲覧でき、終点がないグリッド)を確認し、オリジナル版かassets/を選択します選択したソース外部ライブラリを取り込めない場合 → B01
W02作業オリジナルのPen実行リンクを開いてエフェクトを確認 → .htmlファイル全体(+_assets/)をプロジェクトにコピーし、クレジットコメントを保持しますプロジェクト内のファイル—
W03判断コピーしたファイルコンテンツ/画像/色のみを変更します適用済みファイル—
W04作業ファイルfile://ではなく、HTTP経由でローカル実行します実行中のページアセットのエラー → パスを再確認します
W05決定論的ページplaywright-verifyで検証します。4方向に強くドラッグする前/途中/後のスクリーンショットを撮り、コンソールを確認します画像+エラーのないコンソールエラー → 修正し、W04を繰り返します

各ステップの詳細(W01~W05の信頼できる情報源):

オリジナル版 — 先に使用(ファイル全体をコピーし、書き直さないでください)

作者によるオリジナルのソースコード(公開されているCodePen=MIT)は、外部リポジトリ Rheinmir/uiux-asset にあります — 実際に動作するギャラリー:https://rheinmir.github.io/uiux-asset/scroll-effects/ 。 手順:実行リンクを開いて正確なエフェクトを確認 → .htmlファイル全体(ファイルが参照している場合は_assets/フォルダーも)をプロジェクトにコピー → 先頭行のクレジットコメントを保持 → コンテンツ/画像/色のみを変更します。file://ではなく、HTTP経由でローカル実行します。 オフラインで一式が必要な場合は、git clone --depth 1 https://github.com/Rheinmir/uiux-assetを実行します。

オリジナルのPen — 作者リンク
Infinite Scrollable and Draggable WebGL Grid — ReGGae実行 · コード
Infinite 3D Poster Scroll Wall — photodow実行 · コード
Infinite Scrolling with Image Cards — jkantner実行 · コード

このスキルのassets/は、独自に作成したバニラ版の簡易実装です — プロジェクトで外部ライブラリを取り込めない場合に使用します。通常は上記のオリジナル版を使用します。

分岐
ID種類条件動作スキップ/失敗再合流先
B01条件付き必須プロジェクトで外部ライブラリを取り込めないassets/demo.html+demo.css+demo.jsを使用し、パラメーターを適用してプレースホルダーを実際の画像に置き換えます(「アセット」セクション)—W04
B02ユーザー選択奥行きのあるエフェクトが必要OrthoをPerspectiveCameraに変更し、wrapの式は維持して、world-unitからpixelへのマッピングのみを変更します—W05
B03条件付き必須表示されるタイル数より多くの異なる画像が必要poolSizeを増やすのではなく、テクスチャアトラスを使用します(UVオフセットを変更)—W05
検証と完了条件

Playwrightで4方向へのドラッグ前/途中/後のスクリーンショットをすべて撮影し、グリッドにポップインがなく、背景ページがスクロールせず、コンソールにエラーがない状態で完了です(WebGLエラーは表示されないことが多いため、画像を見るだけでなく、必ずコンソールを確認してください)。

例
  • 成功例:「ポートフォリオ用に無限スクロールするポスターウォールを作って」→ Rheinmir/uiux-assetからinfinite-3d-poster-scroll-wall.htmlをコピーし、画像を置き換え、HTTP経由でローカル実行して、Playwrightで4方向にドラッグします → タイルが滑らかに飛び移り、コンソールにエラーがありません。
  • 境界/失敗例: assets/demo.jsを使っているが、wheelを{ passive: false }なしで登録している → preventDefault()が黙って無視され、背景ページもグリッドと一緒にスクロールします → 検証に失敗するため、フラグとtouch-action: noneを追加して再実行します。
参照 — アセット

assets/demo.html+assets/demo.css+assets/demo.js — 読んでから適用します(InfiniteGridの初期化時にgridSize/tileSize/gap/poolSizeを変更し、_drawPlaceholder()を、canvasのdata URLではなくURLをTHREE.TextureLoaderで読み込む実際の画像に置き換えます)。デモはプレースホルダー(canvas上の数字+ハッシュカラー)を自動描画するため、画像やネットワークがなくてもすぐに実行できます。

リファレンス — 仕組み
  • メッシュは固定で、追加生成しません:gridSize × gridSize 個のメッシュ(PlaneGeometry は 1 インスタンスを共有し、各メッシュには個別のマテリアルを使用)を初期化時に一度だけ構築します。実行中にメッシュを追加・削除することは決してありません。「未表示の領域までドラッグしたら新しいメッシュを生成する」という素朴な方法とはまったく異なります。
  • 仮想オフセットを使い、カメラを無限遠まで移動させません:ドラッグ/スクロールでは offset.x/offset.y(理論上はいくらでも大きくでき、実際の上限には達しない浮動小数点数)に加算するだけです。カメラは動きません。
  • 剰余によるラップ:各メッシュには固定のローカル座標 localX/localY(元の N×N グリッド内の位置)があります。各フレームでの実際の表示位置は wrap(localX - offset.x) です。ここで wrap(raw) = ((raw + half) % full + full) % full - half(full = gridSize*pitch、half = full/2)とし、無限に広がる座標を中心付近の表示範囲に折り返します。この手法の標準的な剰余の式です。
  • タイルが飛び越える際のテクスチャ再利用:各メッシュの material.map を変更するのは、「仮想タイルのインデックス」が変わったときだけです(wrapped_position + offset から求め、pitch 単位に丸めます)。つまり、タイルが片側の表示範囲から外れ、反対側に再び現れるタイミングです。テクスチャは固定プール(THREE.TextureLoader で一度だけ読み込む poolSize 個のテクスチャ)から取得し、飛び越えるたびに新しいテクスチャを作成することはありません。
  • ネットワークに依存しないプレースホルダー:プール内のテクスチャは、数字と色を描画する <canvas> から生成します(色相を golden-angle で分散させ、見た目で区別しやすくします)。その後 canvas.toDataURL() を使い、引き続き THREE.TextureLoader.load(dataUrl) で読み込みます。これによりデモはオフラインで動作しつつ、実際の画像を読み込む場合と同じ API 経由の読み込みフローを維持します(実際の画像に置き換える際は URL を変更するだけで、コードの流れは変わりません)。
  • 慣性:ポインタのドラッグ中は velocity(最後の pointermove における移動量)を記録します。手を離すと、閾値を下回るまで各フレームで offset += velocity; velocity *= damping (0.92) とします。物理を忠実にシミュレートするのではなく、0 に向けて徐々に lerp します。
  • クリーンアップ:destroy() では pointerdown/pointermove/pointerup/pointercancel/wheel/resize リスナーを解除し、各マテリアルを dispose し、共有ジオメトリを一度だけ dispose し、プール内の各テクスチャを一度だけ dispose します(テクスチャは複数のメッシュで共有されるため、メッシュごとに dispose してはいけません)。さらに、レンダラーを dispose し、canvas を DOM から取り除きます。

オリジン

コメント