threejs-particle-morph について
threejs-particle-morphは、スクロールに合わせて粒子が画像やロゴに集まり、逆方向のスクロールで再び散らばるウェブ効果を作るThree.jsスキルです。元画像のピクセルをサンプリングして粒子の目標位置を作り、粒子関連の設定を調整できます。
主な機能
- 01画像ピクセルから粒子の目標を生成
- 02スクロール進捗に応じて粒子が集合
- 03逆スクロールで粒子が分散
- 04粒子数とサンプリング設定を変更
- 05リソースのクリーンアップ方法を提供
活用シーン
- 01スクロールに合わせてポートフォリオのロゴを形成
- 02粒子で画像を組み立てるスクロール効果
- 03逆スクロールで画像を分解
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rheinmir/ setup --skill threejs-particle-morphインストール後、Claude Code で/threejs-particle-morphを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/demo.cssdemo.htmldemo.js詳細
スキル:threejs-particle-morph
純粋な Three.js コンポーネントです。3D 空間にランダムに散らばった数千個のパーティクルが、ページのスクロールに合わせて徐々に集まり、ピクセルからサンプリングした画像になり、逆方向にスクロールすると再び散らばります。
WHAT
目的とコンテキスト
- 目的: Three.js のパーティクルクラウド(
THREE.Points+BufferGeometry)を構築します。スクロールの進行度に合わせてランダムな位置から画像/ロゴへ集まり、逆方向にスクロールすると再び散らばります。元の実装をそのままコピーするのを優先し、assets/のバニラ版は代替手段です。 - 使用するタイミング:
- スクロールすると「画像が粒子から組み上がる」演出を加えたいヒーローセクション/ポートフォリオ。
- ユーザーが「particle image」「粒子が画像に集まる」「point cloud morph」「three.js particle scroll」と言った場合。
- 対象外: ズームとブラーによる滑らかな画像遷移には使用しません(これは
blurの用途です。単一の plane にシェーダーを適用し、個々のパーティクルはありません)。また、WebGL を必要としない軽いスクロールエフェクトにも使用しません(これはscroll-effectsの用途です)。作者の元のコードを書き直してはいけません。
メンタルモデル
元画像 → 非表示 canvas(sampleResolution)→ getImageData + stride + alphaThreshold → ターゲット(+ 色) · 開始位置はランダム → スクロール → targetProgress → progress(減衰)→ position = lerp(starts, targets, progress) → PointsMaterial で描画
入力と出力の契約
| フィールド | 必須? | 意味 | |
|---|---|---|---|
| In | 元画像/ロゴ(imageUrl) | いいえ | 指定がない場合 → デモがサンプル用に非表示 canvas へサンプルテキストを自動描画します |
| In | particleCount、particleSize、sampleResolution、alphaThreshold | いいえ | ParticleMorph の初期化パラメーター |
| In | プロジェクトの制約(外部ライブラリを導入できるか) | はい | 元の実装(デフォルト)を使うか、assets/ のバニラ版を使うかを決定します |
| Out | プロジェクト内のコンポーネント | はい | 元のファイルをそのままコピー(クレジットコメントを保持)するか、assets/demo.* を適応したもの。destroy() を備えます |
| Out | 検証結果 | はい | progress 0/0.5/1 のスクリーンショットと、エラーのないコンソール |
ルールと機能
- RULE-01 (MUST):パフォーマンス:パーティクル数を増やすほど、CPU(毎フレーム
positionを書き換えるループ)と GPU の負荷が高くなります。ミドルレンジのマシンで推奨される安全な上限は 5,000~10,000 個以下です。particleCountを増やす前に、DevTools の Performance/FPS メーターで測定してください。推測で判断してはいけません。特に、大きくて細部の多い画像用にパーティクルを追加する場合は注意してください(sampleResolutionを上げると、init()時のサンプリングコストも増えます。一度だけ実行されます)。 - RULE-02 (MUST):Three.js(
three)は npm でインストールし(npm install three)、本番環境ではバンドラー経由でインポートしてください。デモはバンドラーなしで単独実行するため、CDN(unpkg.com/[email protected])を指すimportmapを使用します。blurがかつて行っていたように、バージョンを固定した古い CDN 版を使用してはいけません。実際のプロジェクトに組み込む際は、<script type="importmap">を削除し、npm install threeを実行して、import * as THREE from 'three'をバンドラー経由のインポートに変更してください。 - RULE-03 (SHOULD):
_buildSamples()のalphaThreshold/明るさフィルターは、透明または暗い背景のピクセルを除外します。透明な背景の画像(PNG ロゴ)は、単色の背景画像(JPEG)よりすっきりした結果になります。単色背景の画像は、事前に切り抜かない限り背景も含めてサンプリングされます。 - RULE-04 (MUST):
playwright-verifyで検証してください。3 つの位置までスクロールし、progress 0/0.5/1 のスクリーンショットを撮影します。コンソールも確認してください(WebGL コンテキストのエラーは、通常は明示的な JS エラーを投げずに発生します)。 - RULE-05 (MUST):まず元の実装を使用し、ファイルをそのままコピーしてください。書き直してはいけません。先頭行のクレジットコメントを保持し、コンテンツ/画像/色だけを変更してください。ローカルでは HTTP 経由で実行し、
file://は使用しないでください。 - 機能:公開されている外部リポジトリからアセットを読み取り/コピーする。フロントエンドのファイルをプロジェクトに書き込む。HTTP 経由でページを起動し、ヘッドレスブラウザーでスクリーンショットを撮影してコンソールを確認する。
失敗時の境界
- 元のリンクを開けない/外部ライブラリを取得できない →
assets/のバニラ版を使用します(分岐 B01)。 particleCountを増やした際に FPS が低下する → これ以上の増加は blocked とし、再測定して 5,000~10,000 個以下を維持します。- コンソールに WebGL コンテキストエラーが表示される(通常は目立たずに発生) → 検証は failed です。引き渡してはいけません。
- 単色背景の画像で背景までサンプリングされる → 事前に切り抜くか、透明な背景の PNG を使用してください。
HOW
メインワークフロー
| 手順 | 種類 | 入力 | アクション | 出力/終了条件 | 失敗時/次の手順 |
|---|---|---|---|---|---|
| W01 | 判断 | プロジェクトの制約 | 使用するソースを選択します。元の Rheinmir/uiux-asset(デフォルト)か、vanilla 版の assets/ | 選択したソース | 外部ライブラリを追加しない場合 → B01 |
| W02 | エフェクト | 実行リンク+コード | 実行リンクを開いてエフェクトを確認し、.html ファイル全体(+_assets/)をプロジェクトにコピーします | プロジェクト内のファイル、著作権表示コメントあり | — |
| W03 | エフェクト | ファイル | 内容/画像/色のみを変更します(vanilla 版では imageUrl/particleCount/particleSize も変更します) | 適応済みコンポーネント | — |
| W04 | 決定的 | HTTP 経由のページ | playwright-verify で検証します。progress 0/0.5/1 のスクリーンショットを撮影し、コンソールを確認します | 画像 3 枚+コンソールにエラーなし | WebGL/FPS の問題 → 修正して W03 を繰り返します |
| W05 | エフェクト | 実際のプロジェクト | 本番用に組み込みます。importmap を削除し、npm install three を実行して、bundler 経由で import します | ビルドが実行される | — |
各手順の詳細(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 — 作者 | リンク |
|---|---|
| Scroll-Driven Particle WebGL Image Matrix — ycw | 実行 · コード |
| GSAP ScrollSmoother and Three.js — cmalven | 実行 · コード |
| GSAP ScrollTrigger Disintegration Effect — dev_loop | 実行 · コード |
このスキルの assets/ は、独自に作成した簡易版の vanilla 実装です。プロジェクトで外部ライブラリを追加できない場合にのみ使用し、通常は上記の元のバージョンを使用してください。
Assets
assets/demo.html + assets/demo.css + assets/demo.js — 内容を確認してから適応します(ParticleMorph の初期化時に imageUrl、particleCount、particleSize を変更します)。Demo は imageUrl が渡されない場合に、非表示の canvas にサンプル文字列を自動描画してサンプリングするため、画像やネットワーク接続がなくてもすぐに実行できます。
仕組み
- 画像のサンプリング:元画像(または手描きのキャンバス)を、サイズが
sampleResolutionの非表示の<canvas>に描画し、getImageData()を読み取ります。ピクセルを間引きながら走査します(strideは、希望するparticleCountと総ピクセル数から算出します)。すべてのピクセルを取得するのは負荷が高すぎるため避けます。十分に明るく、透明ではない(alphaThreshold)ピクセルを、ターゲット座標と RGB カラーとして保持します。 - パーティクル:サンプリングした各座標が、
THREE.Points(BufferGeometryのposition属性)内の 1 つのパーティクルになります。初期位置(starts)は、広い空間内にランダムに配置します(「爆発」状態)。ターゲット位置(targets)はサンプリングしたピクセル座標です。これらは個別の 2 つのFloat32Arrayに並行して保存します(GPU の属性 2 つではありません)。 - スクロール → 進行度:スクロールリスナーは 1 つだけ使用し、rAF でスロットリングします(GSAP/ScrollTrigger は使用しません。このデモでは、
gsap-scrolltrigger-pinとの依存関係の重複を避けるため、意図的にプレーンな実装にしています)。scrollY / (scrollHeight - innerHeight)からtargetProgressを算出します。 - 各フレームの Lerp:単純なダンピングで
progressをtargetProgressに徐々に近づけます(scrollY自体を直接アニメーションするわけではありません)。その後、各要素についてposition[i] = lerp(starts[i], targets[i], progress)を設定し、geometry.attributes.position.needsUpdate = trueにします。 - 色とサイズ:
PointsMaterial({ size, sizeAttenuation: true, vertexColors: true })を使用します。元画像の色は静的なcolor属性で保持します(構築時に一度だけ設定し、進行度に応じて変更しません)。 - クリーンアップ:
destroy()メソッドを持つクラスとして実装します。requestAnimationFrameをキャンセルし、scroll/resizeリスナーを削除して、geometry.dispose()、material.dispose()、renderer.dispose()を呼び出します。
ブランチ
| ID | 種類 | 条件 | 動作 | スキップ/失敗 | 再合流 |
|---|---|---|---|---|---|
| B01 | conditional_required | プロジェクトで外部ライブラリを使用できない | assets/demo.html + assets/demo.css + assets/demo.js を使用し、ParticleMorph の初期化パラメーターを適用します | — | W04 |
| B02 | capability_optional | オフライン用の一式が必要 | git clone --depth 1 https://github.com/Rheinmir/uiux-asset | ギャラリーにネットワーク接続できる場合 → スキップ | W02 |
検証と終了条件
実際に実行して確認します:playwright-verify でスクロール位置 3 か所のスクリーンショットを撮影し、コンソールを読み取ります(WebGL のエラーは通知されないことがよくあります)。パフォーマンスは DevTools Performance/FPS メーターで測定し、推測しません。目視で確認し、progress 1 で画像が正しい形にまとまり、progress 0 で散らばることを確認します。画像 3 点が正しく、コンソールに問題がなければ終了します。
例
- 適切な例: スクロール時に PNG ロゴがまとまるポートフォリオのヒーローセクションが必要 → ギャラリーから
scroll-driven-particle-webgl-image-matrix.htmlをそのままコピーし、クレジットコメントを残してロゴ画像を変更します → 0/0.5/1 でスクリーンショットを撮影し、粒子が散らばった状態 → 中間状態 → 完成したロゴになることと、コンソールに問題がないことを確認します。 - 境界/失敗例: プロジェクトで外部 CDN ライブラリが禁止されている →
assets/demo.jsを使用します。particleCountを 30.000 にすると FPS が低下する → 測定結果に基づいて ≤ 10.000 に下げ、再度検証します。 - 境界例: ユーザーが、1 つの平面上でズームブラーを使って画像 A→B に切り替えたい → このスキルは使わず、
blurに切り替えます。