なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

@react-three/fiber で複数の Canvas 間で OrbitControls の操作を共有したい

React で Three.js を扱うためのライブラリ、 @react-three/fiber で、タイトル通りのことをしたい場合の話。

r3f.docs.pmnd.rs

例えば、画面を4分割して4種類の 3D モデルを同じ角度から比較したい、などと言ったときに使える、複数の Canvas 間で OrbitControls の操作を共有する方法。
やり方は簡単で、以下のように、 domElement に共通の DOM Element を渡してあげるだけでよい。

import React from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import { Scene } from './components/Scene';

function App() {
  const overlayRef = React.useRef<HTMLDivElement>(null);
  const [element, setElement] = React.useState<HTMLDivElement>();

  React.useEffect(() => {
    if (overlayRef.current) {
      setElement(overlayRef.current); // current 直に渡すと変更が検出されなくて動かないので注意
    }
  }, []);

  return (
    <div className="w-full h-screen bg-gray-900 relative">
      {/* Transparent Overlay */}
      <div ref={overlayRef} className="absolute inset-0 bg-transparent z-10" />

      <div className="grid grid-cols-2 h-full gap-4 p-4 relative z-0">
        {/* First Canvas */}
        <div className="relative rounded-lg overflow-hidden shadow-xl bg-neutral-600">
          <Canvas camera={{ position: [0, 0, 6] }} className="w-full h-full">
            <Scene />
            <OrbitControls domElement={element} />
          </Canvas>
          <div className="absolute top-4 left-4 text-white">
            <h2 className="text-xl font-bold">Scene 1</h2>
            <p className="text-sm text-gray-300">Click and drag to rotate</p>
          </div>
        </div>

        {/* Second Canvas */}
        <div className="relative rounded-lg overflow-hidden shadow-xl bg-neutral-800">
          <Canvas camera={{ position: [0, 0, 6] }} className="w-full h-full">
            <Scene />
            <OrbitControls domElement={element} />
          </Canvas>
          <div className="absolute top-4 left-4 text-white">
            <h2 className="text-xl font-bold">Scene 2</h2>
            <p className="text-sm text-gray-300">Click and drag to rotate</p>
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;

渡す DOM Element は Canvas である必要は無く、 div 要素でもなんでも良い。無駄に Canvas を敷くのはパフォーマンス的によろしくないと思うので、 div 要素を敷くのがよいと思う。 あとは div 要素の上をぐりぐりマウスでいじれば、左右の2つの Canvas で操作が同期していることが分かる。

以下は実際に動く StackBlitz、1分程度待てば動くサンプルが確認できる。

stackblitz.com

ということで、メモでした。

参考: