[HTML, JS, Three.js] Canvasの解像度と表示する解像度を別々に設定する
ThreeJSを使っているとき、Canvasの解像度と表示する解像度を別々に設定する方法を教えてください。
例えば、Canvasの解像度はwidth: 1000px, height: 1000pxで固定しつつ、ページに表示するときはwidth: 100%, height: 100%にしたいです。
Three.jsでは、Canvasの解像度と表示解像度を別々に設定する方法が提供されています。
まず、Canvas要素を作成します。このとき、固定解像度を設定します。
const canvas = document.createElement('canvas'); canvas.width = 1000; canvas.height = 1000;
次に、Three.jsのレンダラーを作成し、表示解像度を設定します。
const renderer = new THREE.WebGLRenderer({canvas: canvas}); renderer.setSize(window.innerWidth, window.innerHeight);
これにより、Canvasの解像度は固定されたままで、画面上にはページ全体にフィットするように表示されます。窓のサイズが変わると、レンダラーも表示解像度を自動的に更新します。
上記のコードをサンプルに応用して、具体的なアプリケーションに組み込んでみてください。それにはThree.jsのバージョンr126以降が必要です。
以下のコードではCanvasタグがspan タグからはみ出します。Canvasの解像度は変化させずに、レイアウトはspanタグ内に収まるように修正してください。
import { type FC } from "react" type Props = { canvasRef: React.RefObject<HTMLCanvasElement> } export const PreviewCanvas: FC<Props> = (props) => { return ( <span style={{ width: "100%", height: "100%" }}> <canvas ref={props.canvasRef} width={1000} height={1000} /> </span> ) }
以下のように修正することができます:
import { FC } from "react" type Props = { canvasRef: React.RefObject<HTMLCanvasElement> } export const PreviewCanvas: FC<Props> = (props) => { const containerStyle = { position: "relative", width: "100%", height: "0", paddingBottom: "100%", // アスペクト比を維持するための padding } const canvasStyle = { position: "absolute", top: "0", left: "0", width: "1000px", height: "1000px", } return ( <div style={containerStyle}> <canvas ref={props.canvasRef} style={canvasStyle} /> </div> ) }
上記の修正では、wrapperとなるdiv
要素を作成し、そこにcanvas
要素を配置します。div
要素はアスペクト比を維持するためにpaddingを持つように設定されています。canvas
要素のサイズは直接指定されており、Canvasの解像度(1000x1000)を維持するために固定されています。これにより、canvas
要素はspan
タグ内に収まるようになります。
↑機能しない
Threejsで正方形のCanvasにレンダリングを行うコード例を作成してください。ただし、レスポンシブ対応するとします。
以下に、Three.jsを使用して正方形のCanvasにレンダリングを行うコードの例を示します。また、レスポンシブな設定も追加しています。
import * as React from "react"; import { useEffect, useRef } from "react"; import * as THREE from "three"; // コンポーネント const CanvasRenderer: React.FC = () => { const canvasRef = useRef<HTMLCanvasElement>(null); useEffect(() => { let animationFrameId: number; const canvas = canvasRef.current; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ canvas }); // Canvasのサイズに合わせてレンダラーを設定する関数 const resizeRenderer = () => { const width = canvas.clientWidth; const height = canvas.clientHeight; renderer.setSize(width, height); camera.aspect = 1; camera.updateProjectionMatrix(); }; // シーンに立方体を作成 const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); // アニメーションループ const animate = () => { cube.rotation.x += 0.01; cube.rotation.y += 0.01; animationFrameId = requestAnimationFrame(animate); renderer.render(scene, camera); }; // マウント時の初期設定 renderer.setSize(canvas.clientWidth, canvas.clientHeight); camera.position.z = 5; // ウィンドウのリサイズ時にレンダラーを調整するイベントリスナー window.addEventListener("resize", resizeRenderer); // マウント時のアニメーション開始 animate(); // アンマウント時の後始末 return () => { cancelAnimationFrame(animationFrameId); window.removeEventListener("resize", resizeRenderer); }; }, []); return <canvas ref={canvasRef} />; }; export default CanvasRenderer;
このコードでは、ReactのコンポーネントとしてThree.jsのレンダリングを行っています。useRef
フックを使用してキャンバス要素の参照を取得し、その参照をWebGLRenderer
のコンストラクタに渡しています。Canvasのサイズは、useEffect
フックを使用して初期設定とウィンドウのリサイズ時に調整されます。
また、BoxGeometry
とMeshBasicMaterial
を使用して立方体を作成し、その立方体をシーンに追加しています。アニメーションループでは、立方体を回転させつつ、requestAnimationFrame
を使用して連続的にレンダリングを行っています。
最後に、アンマウント時にアニメーションフレームのキャンセルとイベントリスナーの削除を行うためのクリーンアップ関数を返しています。
このコンポーネントを呼び出すと、正方形のCanvasにThree.jsによるレンダリングが行われます。