CSS Gridレイアウト内のHTML Canvas(react-konva)をレスポンシブ対応させる方法
今回作りたかったのは、CSS GridのGridアイテム内にHTML Canvasを組み込んだレイアウトです。でも、これがなかなかうまくいかず、悪戦苦闘してようやく対応させることができました。以下はその備忘録です。
以下は基本的なコードの抜粋です。Gridレイアウトは、1024px以上のディスプレイでは2カラム、それ以下のサイズでは1カラムのレイアウトになります。CSS部分は、Tailwind CSSを使用しています。また、Canvasを記述するのに、react-konvaを使用しています。
react-konvaについては説明を省きますが、簡単に言うと、CanvasをReactでも手軽に記述できるようにしたパッケージです。react-konvaを使用しないCanvas、つまりVanilla Canvasでも、記述方法が違うだけでやることは同じです。
以下のコードでは、useLayoutEffectフックの中でcheckSize関数をEventListernerとして登録し、ウィンドウがリサイズされると、checkSize関数が呼び出される仕組みになっています。そしてcheckSize関数で取得したウィンドウのサイズを元に、Canvasエレメントのscale属性(scaleXとscaleY)を調整しています。
親コンポーネント(抜粋)
// 2カラム→1カラムのGridレイアウト
<div className="grid grid-cols-1 lg:grid-cols-2 mx-auto max-w-7xl">
<Konva />
<div>
Lorem ipsum...
</div>
</div>
子コンポーネント
// Konvaコンポーネント
import React from 'react';
import { Stage, Layer, Circle } from 'react-konva';
const BASE_WIDTH = 800;
const BASE_HEIGHT = 800;
export default function Konva() {
const [dimensions, setDimensions] = React.useState({
width: 1,
height: 1
});
const divRef = React.useRef(null);
const checkSize = () => {
if (divRef.current?.offsetHeight && divRef.current?.offsetWidth) {
setDimensions({
width: divRef.current.offsetWidth,
height: divRef.current.offsetHeight
});
}
};
React.useLayoutEffect(() => {
checkSize();
window.addEventListener('resize', checkSize);
return () => window.removeEventListener('resize', checkSize);
}, []);
const scale = dimensions.width / BASE_WIDTH > 1 ? 1 : dimensions.width / BASE_WIDTH;
return (
<div
ref={divRef}
>
<Stage
width={BASE_WIDTH}
height={BASE_HEIGHT * scale}
scaleX={scale}
scaleY={scale}
>
<Layer>
<Circle
x={BASE_WIDTH / 2}
y={BASE_HEIGHT / 2}
radius={300}
/>
</Layer>
</Stage>
</div>
);
}
原因と対処方法
Canvas内に描かれている円(Circleエレメント)をレスポンシブ対応、つまりウィンドウのサイズに応じて拡大縮小したかったのですが、うまく拡大縮小してくれません。具体的に言うと、ウィンドウのサイズが小さくなって1カラムになると、円のサイズが元のサイズのまま動きません。
どうやら、個々のGridアイテム内にあるCanvasのサイズが、元々のGridアイテムのサイズを超えるとレスポンシブにならないようです。Stageエレメントのサイズをいじってみたりしたのですが、うまくいかず難儀しました。
色々と調べてみた結果、Stageエレメントの親となるdivエレメントのclassName属性に、min-w-0(min-width: 0) を追加したら、うまくレスポンシブしてくれました。min-widthは必ずしも0である必要はなく、任意の数値で構いません。
<div
ref={divRef} className="min-w-0" // min-width: 0
>
総括すると、CanvasはCSS Gridレイアウトとの親和性があまりいいとは言えません。CSS Grid内に図形等を描画してレスポンシブ対応させるなら、SVGの方が適していると思います。