コード
function Canvas() { const [mousePressed, setMousePressed] = useState(false); const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); const [origin, setOrigin] = useState({ x: 0, y: 0 }); const [logScale, setLogScale] = useState(0); function translateCanvas(x: number, y: number) { if (mousePressed) { setOrigin({ x: origin.x + x - mousePos.x, y: origin.y + y - mousePos.y }); } setMousePos({ x, y }); } function scaleCanvas(delta: number) { let nextX = mousePos.x + Math.pow(1.1, delta/100) * (origin.x - mousePos.x); let nextY = mousePos.y + Math.pow(1.1, delta/100) * (origin.y - mousePos.y); let nextLogScale = logScale + delta / 100; if (-50 < nextLogScale && nextLogScale < 50) { setOrigin({ x: nextX, y: nextY }); setLogScale(nextLogScale); } } useEffect( () => { const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = canvas.getContext('2d') as CanvasRenderingContext2D; context.resetTransform(); context.clearRect(0, 0, canvas.width, canvas.height); context.translate(origin.x, origin.y); context.scale(Math.pow(1.1, logScale), Math.pow(1.1, logScale)); // 描画 }, [origin, logScale] ); return ( <canvas onMouseDown={(e) => setMousePressed(true)} onMouseUp={(e) => setMousePressed(false)} onMouseMove={ (e) => { let target = e.currentTarget.getBoundingClientRect(); translateCanvas(e.clientX - target.left, e.clientY - target.top); } } onWheel={(e) => scaleCanvas(-e.deltaY)} ></canvas> ) } export default Canvas;
方針
Canvasの原点の位置origin
とlogスケールlogScale
を持って置く。拡大率はホイールの回転数について指数関数にしておくと滑らかになる気がするのでlogスケールで管理する。useEffect
を使って、origin
とlogScale
が変更されたら再描画させる。
const [origin, setOrigin] = useState({ x: 0, y: 0 }); const [logScale, setLogScale] = useState(0);
変換の順番に注意する。原点を移動した上でスケールを変更する。
useEffect( () => { const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = canvas.getContext('2d') as CanvasRenderingContext2D; context.resetTransform(); context.clearRect(0, 0, canvas.width, canvas.height); context.translate(origin.x, origin.y); context.scale(Math.pow(1.1, logScale), Math.pow(1.1, logScale)); // 描画 }, [origin, logScale] );
ドラッグで平行移動
現在マウスが押されているかをmousePressed
で管理。マウスが押されている状態でMouseMove
が発生したら、Canvasの原点に現在のマウスの位置と直前の位置の差分を足し込む。
function translateCanvas(x: number, y: number) { if (mousePressed) { setOrigin({ x: origin.x + x - mousePos.x, y: origin.y + y - mousePos.y }); } setMousePos({ x, y }); }
ホイールで拡大縮小
マウスの位置を中心に拡大縮小する。Canvasの原点は、現在のマウスの位置と原点の差を拡大縮小した上で元の座標に足す。logスケールは単純に足す。
function scaleCanvas(delta: number) { let nextX = mousePos.x + Math.pow(1.1, delta/100) * (origin.x - mousePos.x); let nextY = mousePos.y + Math.pow(1.1, delta/100) * (origin.y - mousePos.y); let nextLogScale = logScale + delta / 100; if (-50 < nextLogScale && nextLogScale < 50) { setOrigin({ x: nextX, y: nextY }); setLogScale(nextLogScale); } }