Next.jsで3D
3 ファイバーの周りには、ライブラリ、ヘルパー、抽象化が満載の活気に満ちた広範なエコ システムがあります。
@react-three/drei– 役に立つヘルパー、それ自体がエコシステムです
@react-three/gltfjsx– GLTFをJSXコンポーネントに変換します
@react-three/postprocessing– 後処理効果
@react-three/test-renderer– ノードのユニットテスト用
@react-three/flex– react-three-fiber 用の flexbox
@react-three/xr– VR/ARコントローラーとイベント
@react-three/csg– 構成的立体幾何学
@react-three/rapier– Rapierを使用した3D物理
@react-three/cannon– キャノンを使った3D物理
@react-three/p2– P2を使用した2D物理学
@react-three/a11y– あなたのシーンのための本当のa11y
@react-three/gpu-pathtracer– リアルなパストレース
create-r3f-app next– nextjs スターター
lamina– レイヤーベースのシェーダーマテリアル
zustand– フラックスベースの状態管理
jotai– アトムベースの状態管理
valtio– プロキシベースの状態管理
react-spring– スプリング物理ベースのアニメーションライブラリ
framer-motion-3d– 人気のアニメーションライブラリ、フレーマーモーション
use-gesture– マウス/タッチジェスチャー
leva– 数秒でGUIコントロールを作成
maath– 数学の助けとなるキッチンシンク
miniplex– ECS(エンティティ管理システム)
composer-suite– シェーダー、パーティクル、エフェクト、ゲームメカニクスの作成
git update-ref -d HEAD
をすることで、初回コミットをリセットできる。
初回のコミットを取り消したいときにはgit update-refを使う
Canvasのcamera
プロパティの意味は以下の通りらしい。
- fov:カメラの視野の垂直方向、下から上への度数。
- near:平面付近のカメラのフラストレーション。
- far:カメラの距方平面。(nearより大きい値じゃないとダメらしい)
- position:オブジェクトのローカル位置。
near
とfar
がイマイチわからんな。
"use client";
import type { CanvasProps } from "@react-three/fiber";
import { Canvas } from "@react-three/fiber";
export function Canvas3D(props: CanvasProps) {
return <Canvas {...props} />;
}
<Canvas3D camera={{ fov: 75, near: 0.1, far: 1000, position: [0, 0, 1] }}>
// ...
</Canvas3D>
ユーザーからの操作イベント
<mesh
onClick={(e) => console.log('click')}
onContextMenu={(e) => console.log('context menu')}
onDoubleClick={(e) => console.log('double click')}
onWheel={(e) => console.log('wheel spins')}
onPointerUp={(e) => console.log('up')}
onPointerDown={(e) => console.log('down')}
onPointerOver={(e) => console.log('over')}
onPointerOut={(e) => console.log('out')}
onPointerEnter={(e) => console.log('enter')}
onPointerLeave={(e) => console.log('leave')}
onPointerMove={(e) => console.log('move')}
onPointerMissed={() => console.log('missed')}
onUpdate={(self) => console.log('props have been updated')}
/>
const { actions, mixer } = useAnimations(animations, group)
useEffect(() => {
actions.spin?.setLoop(THREE.LoopRepeat, 2).play()
mixer.addEventListener('loop', (e) => {
// アニメーションがループされた時に発火
console.log('loop', e)
})
mixer.addEventListener('finished', (e) => {
// アニメーションが終了した時に発火
console.log('finished', e)
})
}, [actions, mixer])
tailwindでアニメーションを作成するのに、役に立つかも。
NotSupportedError: Failed to set the 'playbackRate' property on 'HTMLMediaElement': The provided playback rate (17) is not in the supported playback range.
というエラーが発生した。どうやら、videoタグのplayBackRate
の最大値は16らしい。
16に設定したところエラーが発生しないが、17にするとエラーが発生した。
framer-motion
を利用して、コンポーネントのアンマウント時にアニメーションを再生するようにしようと考えた時に、Suspense
との兼ね合いがどうなるのかが気になる。
試した感じ、うまくいかなかった。
そのため、ここにあるようにContext
使った実装にしても良いかもしれない。
3Dモデルにframer-motion-3dで透過アニメーションを適用するには、<mesh>
タグにアニメーションを適応するのではなく、<mesh>
タグの子に<material>
タグを作成して、そこに透過アニメーションを適応する必要がある。なぜなら、<mesh>
タグにはopacity
というプロパティがないからである。
また、transparent
をtrueにしないと<material>
のopacity
が反映されないので、これは必須である。
<group ref={group} {...props}>
<group name="Scene" dispose={null}>
<motion.mesh
name="earth"
geometry={nodes.earth.geometry}
material={materials.material}
material-transparent={true}
animate={{
opacity: [0, 1, 0], // 点滅のために透明度を1から0に変化させる
}}
transition={{
duration: 1, // 1秒かけて点滅
repeat: Infinity, // 永遠に繰り返す
repeatType: 'reverse', // 透明度を1から0、そして0から1に戻す
}}
/>
</group>
</group>
<group ref={group} {...props}>
<group name="Scene" dispose={null}>
<motion.mesh
name="earth"
geometry={nodes.earth.geometry}
>
<motion.material
{...materials.material}
transparent
animate={{
opacity: [0, 1, 0], // 点滅のために透明度を1から0に変化させる
}}
transition={{
duration: 1, // 1秒かけて点滅
repeat: Infinity, // 永遠に繰り返す
repeatType: 'reverse', // 透明度を1から0、そして0から1に戻す
}}
/>
</motion.mesh>
</group>
</group>
Three.jsで3Dモデルをキャッシュするには、サービスワーカーを使ってfetchイベントを監視する必要があるのか。
self.addEventListener("fetch", (event) => {
if (event.request.method !== "GET") return;
event.respondWith(
(async () => {
const url = event.request.url;
const isGlb = url.endsWith(".glb");
if (isGlb) {
// Try to get the response from a cache.
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(event.request);
if (cachedResponse) {
// If we found a match in the cache, return it, but also
// update the entry in the cache in the background.
event.waitUntil(cache.add(event.request));
return cachedResponse;
}
}
// If we didn't find a match in the cache, use the network.
const res = await fetch(event.request).catch(() =>
caches.match(event.request).then((res) => res)
);
if (isGlb) {
const copyCache = res.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, copyCache);
});
}
return res;
})()
);
});
でも、Three.jsのChacheを有効にすれば、サイトを開いている間はキャッシュ化できるから、いいか?
import type * as THREE from 'three'
THREE.Cache.enabled = true