threejs-impl-react-three-fiber について
React Three Fiber 開発者向けの実装リファレンススキルです。Canvas の設定、フック、イベント、JSX から Three.js シーングラフへの変換を扱い、描画ループやリソース管理の指針も示します。
主な機能
- 01Canvas のレンダラーとカメラ設定
- 02useFrame、useThree などのフック
- 03JSX から Three.js シーンへの変換
- 04ポインターイベントとレイキャスト
- 05描画ループとリソースの最適化
活用シーン
- 01R3F Canvas と描画方式の設定
- 02フレーム単位のアニメーションやシーン処理
- 03メッシュ上のポインター操作の実装
- 04静的シーンの描画や重複生成の削減
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ impertio-studio/ three.js-claude-skill-package --skill threejs-impl-react-three-fiberインストール後、Claude Code で/threejs-impl-react-three-fiberを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.gitkeepreferences/anti-patterns.mdexamples.mdmethods.md詳細
threejs-impl-react-three-fiber
クイックリファレンス
Canvas コンポーネントの Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
gl | Renderer props | (canvas) => Renderer | {} | WebGL レンダラーの設定またはファクトリー |
camera | Camera props | THREE.Camera | { fov: 75, near: 0.1, far: 1000, position: [0,0,5] } | デフォルトのカメラ |
scene | Scene props | THREE.Scene | {} | シーンの設定 |
shadows | boolean | ShadowMapType | false | シャドウマップを有効にする |
raycaster | Raycaster props | {} | レイキャスターの設定 |
frameloop | "always" | "demand" | "never" | "always" | レンダリングループの方式 |
resize | ResizeOptions | { scroll: true, debounce: { scroll: 50, resize: 0 } } | リサイズ時の動作 |
orthographic | boolean | false | OrthographicCamera を使用する |
dpr | number | [min, max] | [1, 2] | デバイスピクセル比 |
linear | boolean | false | リニア色空間 |
flat | boolean | false | トーンマッピングを無効にする |
legacy | boolean | false | 色管理を無効にする |
events | EventManager | R3F default | カスタムイベントマネージャー |
eventSource | HTMLElement | React.RefObject | Parent node | イベントをキャプチャする要素 |
eventPrefix | string | "offset" | 座標のプレフィックス |
onCreated | (state: RootState) => void | -- | 初期化後のコールバック |
onPointerMissed | (event: PointerEvent) => void | -- | クリックがすべてのメッシュを外した場合 |
fallback | React.ReactNode | -- | 初期化中の DOM フォールバック |
Frameloop のモード
| Mode | Behavior |
|---|---|
"always" | requestAnimationFrame を使用して、毎フレーム必ずレンダリングします |
"demand" | invalidate() が呼び出されたときのみレンダリングします。静的なシーンで使用します |
"never" | 自動ではレンダリングしません。呼び出し側が必ず advance(timestamp) を呼び出す必要があります |
重要な警告
絶対に useFrame 内で Three.js オブジェクトを作成しないでください。毎フレームメモリが割り当てられ、GC の負荷が増加します。必ずコールバックの外でオブジェクトを作成するか、useMemo を使用してください。
絶対に useLoader を使用する際に <Suspense> を忘れないでください。Suspense 境界がないと、コンポーネントはサスペンドし、クラッシュします。
絶対に <primitive> を使って同じ Three.js オブジェクトのインスタンスをシーンツリーに複数回追加しないでください。Three.js オブジェクトが持てる親は 1 つだけです。
絶対に 1 つのプロパティだけが必要なときに、セレクターなしで useThree() を使用しないでください。状態オブジェクト全体を取得すると、毎フレーム再レンダリングが発生します。必ず useThree((s) => s.camera) を使用してください。
絶対に 命令型の scene.add() 呼び出しと、R3F の宣言型 JSX ツリーを混在させないでください。R3F はシーングラフを管理するため、命令型の変更によって同期がずれます。
アニメーションには必ず useFrame の delta を使用してください。時間ステップをハードコードすると、フレームレートによって速度にばらつきが生じます。
命令型で作成するジオメトリとマテリアルには必ず useMemo を使用してください。使用しないと、レンダリングのたびに新しいオブジェクトが割り当てられます。
JSX-to-Three.js のマッピング規則
R3F は決定的な規則に従って、JSX を Three.js のシーングラフ操作に変換します。
-
小文字の JSX = Three.js のクラス。
<mesh />はnew THREE.Mesh()を作成します。<meshStandardMaterial />はnew THREE.MeshStandardMaterial()を作成します。 -
args= コンストラクター引数(配列)。<sphereGeometry args={[1, 32, 32]} />はnew THREE.SphereGeometry(1, 32, 32)になります。argsが変更されると、オブジェクトは破棄されて再作成されます。 -
attach= 親プロパティへのバインド。<meshStandardMaterial attach="material" />はparent.material = thisを設定します。ジオメトリは自動的に"geometry"に、マテリアルは"material"にアタッチされます。 -
ネストされたパスにはハイフン区切りの
attachを使用します。attach="shadow-camera"はparent.shadow.camera = thisを設定します。配列のインデックス指定にはattach="material-0"を使用します。 -
関数形式の
attach。 カスタムのバインド/解除には、attach={(parent, self) => { parent.add(self); return () => parent.remove(self); }}を使用します。 -
.set()を受け付けるプロパティでは省略記法を使用できます。position={[1, 2, 3]}はobject.position.set(1, 2, 3)を呼び出します。color="hotpink"はobject.color.set("hotpink")を呼び出します。 -
スカラーの省略記法。
scale={2}はobject.scale.setScalar(2)を呼び出します。 -
ハイフン区切り表記でネストされたプロパティを指定できます。
rotation-x={Math.PI}はobject.rotation.x = Math.PIを設定します。
フック
useFrame
useFrame((state: RootState, delta: number, xrFrame?: XRFrame) => void, priority?: number)
コールバックをレンダーループに登録します。毎フレーム実行されます。
State オブジェクトの主なプロパティ:
| プロパティ | 型 | 説明 |
|---|---|---|
gl | THREE.WebGLRenderer | レンダラー |
scene | THREE.Scene | シーン |
camera | THREE.Camera | アクティブなカメラ |
clock | THREE.Clock | システムクロック |
pointer | THREE.Vector2 | 正規化されたポインター(-1 から +1) |
size | { width, height, top, left } | キャンバスの寸法(px) |
viewport | { width, height, factor, distance, aspect } | カメラ相対のメトリクス |
invalidate | () => void | 要求時モードでのレンダーを要求します |
advance | (timestamp: number) => void | never モードで 1 ティック進めます |
performance | { current, min, max, regress() } | 適応型パフォーマンス |
set | (state) => void | State を直接変更します |
get | () => RootState | リアクティブではない方法で State を読み取ります |
優先度システム: コールバックは優先度の昇順で実行されます。優先度が 0 より大きいコールバックが 1 つでもある場合、R3F は renderer.render() の自動実行を無効にします。最も優先度の高いサブスクライバーは、state.gl.render(state.scene, state.camera) を手動で呼び出す必要があります。負の優先度では自動レンダリングは無効になりません。
useThree
const state = useThree() // full state (re-renders often)
const camera = useThree((state) => state.camera) // selector (re-renders only on change)
RootState(useFrame の State と同じオブジェクト)を返します。プロパティを 1 つだけ使用する場合は、必ずセレクターを使用してください。
useLoader
const result = useLoader(LoaderClass, url, extensions?, onProgress?)
const results = useLoader(LoaderClass, [url1, url2], extensions?)
Suspense ベースのアセット読み込みです。必ず <Suspense fallback={...}> でラップしてください。
- アセットは URL でキャッシュされます。同じ URL を 2 回読み込むと、キャッシュされた結果が返されます。
useLoader.preload(LoaderClass, url)は、コンポーネントのマウント前にプリロードします。- GLTF の結果には
{ nodes, materials, scene, animations }が含まれます。
useGraph
const { nodes, materials } = useGraph(object3D)
Object3D の階層をたどり、名前をキーとするメモ化された { nodes, materials } コレクションを返します。
Primitives と extend()
Primitives
既存の Three.js オブジェクトを宣言的ツリーに挿入します。
<primitive object={existingMesh} position={[10, 0, 0]} />
- 同じオブジェクトのインスタンスを複数回追加しないでください。Three.js オブジェクトが持てる親は 1 つだけです。
- Primitives は自動的に dispose されません。ライフサイクルは呼び出し側が管理する必要があります。
extend()
カスタムの Three.js クラスを JSX 要素として登録します。
import { extend } from '@react-three/fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
extend({ OrbitControls })
// Now usable as <orbitControls args={[camera, domElement]} />
JSX 要素の名前は、登録されたキーのキャメルケース版になります。
イベントシステム
R3F はレイキャスティングによってポインターイベントを実装します。イベントはシーングラフ内をバブリングします。
サポートされているイベント
| イベント | トリガー |
|---|---|
onClick | メッシュ上でポインターをクリック |
onContextMenu | 右クリック/コンテキストメニュー |
onDoubleClick | ダブルクリック |
onPointerUp | ポインターを離す |
onPointerDown | ポインターを押す |
onPointerOver | ポインターがメッシュに入る(継続的に発火) |
onPointerOut | ポインターがメッシュから出る |
onPointerEnter | ポインターがメッシュに入る(一度だけ発火) |
onPointerLeave | ポインターがメッシュから出る(一度だけ発火) |
onPointerMove | メッシュ上でポインターを動かす |
onPointerMissed | クリックがどのメッシュにも当たらない(Canvas レベル) |
onWheel | スクロールホイール |
onUpdate | オブジェクトが新しい props を受け取る |
イベントオブジェクトのプロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
object | THREE.Object3D | 実際にヒットしたメッシュ |
eventObject | THREE.Object3D | イベントハンドラーを持つオブジェクト |
point | THREE.Vector3 | ワールド空間での交差点 |
distance | number | カメラから交差点までの距離 |
uv | THREE.Vector2 | 交差点の UV 座標 |
face | THREE.Face | 交差した面 |
ray | THREE.Ray | 交差判定に使われたレイ |
camera | THREE.Camera | アクティブなカメラ |
intersections | Intersection[] | 交差したすべてのオブジェクト |
delta | number | 下から上へのピクセル距離 |
sourceEvent | Event | 元の DOM イベント |
stopPropagation() | function | 遮蔽されたオブジェクトへのイベント伝播を防止 |
パフォーマンスパターン
破棄
- R3F はコンポーネントのアンマウント時に Three.js オブジェクトの
dispose()を必ず呼び出し、GPU リソースを解放します。 - 自動破棄を防ぐには、要素に
dispose={null}を設定します。オブジェクトを複数のコンポーネント間で共有する場合に使用します。
静的シーン
継続的にアニメーションしないシーン(ダッシュボード、コンフィギュレーター)では、frameloop="demand" と invalidate() を使用します。これにより GPU の処理サイクルを節約できます。
ポータル
import { createPortal } from '@react-three/fiber'
createPortal(children, targetScene)
メインのシーングラフに影響を与えずに、子要素を別のシーン/レイヤーにレンダリングします。
命令型オブジェクトでの useMemo
命令型で作成したジオメトリとマテリアルは、必ず useMemo でラップしてください。
const geometry = useMemo(() => new THREE.TorusKnotGeometry(1, 0.3, 128, 32), [])
useMemo を使わないと、レンダリングのたびに新しいオブジェクトが作成されます。
リファレンスリンク
- references/methods.md -- フックのシグネチャと Canvas API
- references/examples.md -- 動作する R3F コード例
- references/anti-patterns.md -- R3F で避けるべきこと