以前つくったスクロール管理の仕組みをつかって、ページをスクロールするとダンスを踊るサイトをつくりました。
Next.js + Three.jsでページスクロールに連動してダンスを踊るサイトを作りました。
— 君塚史高 (@ki_230) 2021年1月19日
🕺 https://t.co/0kkwD8lqkm pic.twitter.com/61c5XzkPFh
名付けて、ダンスクロールです。
スクロールとアニメーションを連動させる
前回の2Dのパラパラアニメバージョンは、スクロールに連動してクロールするという形で実装しました。
大きな違いとしては、2Dが3Dになったところに目が行きがちなのですが、コマとコマの間が極限まで小さくなったことが挙げられます。
つまり、前回は8枚のパラパラ漫画を動かしていただけなので、コマとコマの間の補完が効かなかったのですが、今回は全2.4秒のアニメーションをスクロールの量に応じて動かしているのでパラパラ漫画感はほとんど感じません。
loader.load( url, (gltf) => { model = gltf.scene; const animations = gltf.animations; if (animations && animations.length) { mixer = new THREE.AnimationMixer(model); for (let i = 0; i < animations.length; i++) { const animation = animations[i]; // アクションクリップをHooksAPIで登録 // ただしアニメーションが複数設定されている場合は最後のものしか登録されない setAction(mixer.clipAction(animation)); } } model.scale.set(1.8, 1.8, 1.8); model.position.set(0, -2, 0); scene.add(gltf.scene); renderer.setAnimationLoop(tick); function tick() { if (mixer){ mixer.update(clock.getDelta()); } renderer.render(scene, camera); } }, (err) => { console.error(err); } );
useEffect(() => { if (action) { action.play(); action.paused = true; // アクションを再生しつつ一時停止状態にしておく } }, [action]);
useEffect(() => { const diff = lastProgress - progress; if (diff < 0) { if (Math.abs(diff) > .99) { setDirection('up'); } else { setDirection('down'); } } else { if (Math.abs(diff) > .99) { setDirection('down'); } else { setDirection('up'); } } warp(); setLastProgress(progress); if (action) { action.time = 2.4 * progress; // ここでアニメーションを進行している } }, [progress]);
要点だけ抜き出すとこんな感じです。
GLTFLoaderの読み込み方法が謎
そもそも、Three.jsのGLTFLoaderが非常に優秀で、mixamoでつくったアニメーション付きのfbx(をBlenderで書き出したglbファイル)を簡単に読み込みつつ、アニメーションをAnimationMixerで管理してくれます。
(mixamoとBlenderを使ったアニメーション付きのglbファイルの作り方は以前紹介したので、ここでは作り方は省略します)
ただ、GLTFLoaderの読み込み方が謎で、npm install後、
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
と読み込むと、
SyntaxError: Cannot use import statement outside a module
と怒られてしまうので、
import { GLTFLoader } from '../../../node_modules/three/examples/jsm/loaders/GLTFLoader';
という感じで読み込んでますが、もっと良いやり方があるような気がしてなりません。
今後の改善点(3Dを使う意味)
現状だとThree.jsで実装する意味がほとんどなくて、3Dのダンスを一旦映像として書き出し、スクロールに連動してvideoタグを動かすのと体験が変わりません。
むしろvideoタグで実装した方が動作や読み込みが軽くなる気がします。
折角3Dオブジェクトを読み込んでいるので、カメラをぐりぐり動かせるように、さくっとOrbitControlsを入れようかとも思ったのですが、スクロールのジェスチャーとバッティングするので諦めました。