はじめに
以前に投稿したCSSで写真コントロールUIを作るをさらに改良して、
画像をズームしたり、マウスドラッグで動かしたりできて、さらに画像のピクセル座標を取得するUIを作成した。
前回はreact/reduxのスタイル制御で実装したが、
今回はマウスでクリックした位置にピンを立てたりといった、
より複雑な画像コントロールを実現するため、
canvas内包のreactライブラリであるreact-konvaを使ってみた。
react-konvaを使うメリット
- 普通にcanvasを使うより断然簡単!
これは他のCanvas内包ライブラリも同じかもしれないが、
画像操作の実装が格段にしやすくなった。
前回の投稿のようにスタイルを操作する時の苦労から解放された。
画像のズームやドラッグをする際は、width, height, 画像の位置を示すx, yを操作すればいい。
これらのパラメータをstateまたは、propsとして管理し、
Imageコンポーネントに渡すだけで、ブラウザ上で思い通りに画像を動かすことができる。
render() {
const { rootWidth, rootHeight } = this.props;
const { imgObj, width, height, position } = this.state;
<div>
<Stage
width={rootWidth}
height={rootHeight}
>
<Layer>
<Image
image={imgObj}
width={width}
height={height}
x={position.x}
y={position.y}
/>
</Layer>
</Stage>
</div>
}
もちろん、普通にcanvasを使うよりも実装はシンプルになる。
canvasを使って簡単な画像表示をやってみたが、
canvasコンポーネントを作って、
そこにImageオブジェクトをpropsとして渡さなくてはならなかった。
/* App.js */
render() {
const canvasProps = {
width: param.width,
height: param.height,
updateCanvas: ctx => {
let imgObj = new Image():
imgObj.src = 'imag_sample.png';
imgObj.onload = () => {
ctx.drawImage(imgObj, x, y, width, height);
}
}
};
return(
<div>
<CanvasComponent {...canvasProps}/>
</div>
);
}
/* CanvasComponent.js */
updateCanvas() {
const { canvas } = this.refs;
const context = canvas.getContext('2d');
this.props.updateCanvas(context);
canvas.addEventListener('click', this.handleClick, false);
....
}
render() {
return(
<canvas
ref="canvas"
width={this.props.width}
height={this.props.height}
></canvas>
);
}
画像のズームはzoomLevelを呼び出し側のComponentで管理、
これをpropsとして画像表示Componentに渡し、
表示Component側でzoomLevelに合わせてwidth, heightを計算させれば、
サイズの管理もしやすくなる。
zoomの中心はデフォルトで画像の左上だったので、
制御もしやすい。
マウス位置を中心にズームするためには、
画像のサイズ変化に合わせて画像を動かさなくてはならないが、
まさにピクセル座標の原点を中心にズームするので、
位置の補正の計算もしやすくなった。
- konvaのAPIをほぼ全てサポート
元々、HTML5のCanvasライブラリとしてオブジェクトの描画機能を豊富に備えたKonva.js、
そのReact版がreact-konvaになる。
自分の経験不足もあるかもしれないが、
何かと画像の扱いに苦労するReactで各種図形オブジェクトをReact Componentとして使えるのは本当に便利。
少し苦労したのがLayerの概念。
一枚の画像上にSVG pathを使ってSVG画像をマッピングし、
ベースになっている画像の動きと連動して、
マッピング画像も遅延なく同期して動かせるか試してみた。
最初はマッピング画像どうしを一つのLayer上で管理し、
ベースの画像は別のLayerとした。
このとき、stateで各画像の位置を管理、
stateを更新することで複数の画像が同期して動くようなレンダリングを試みたものの、
片方の画像をマウスドラッグで動かしたとき、
もう片方が遅れてついてくるようになってしまった。
そこで、今度は一つのStageで全ての画像を描画したところ、
期待通りの動作を実現できた。
react-konvaのデメリット
けっこう困ったのがImageのサイズとStageのサイズの設定。
UIを作るうえで画像の表示領域はある程度指定しなければならない。
その反面、表示する画像のサイズはimage.onloadで読み込むまでは分からない。
また、画像のズーム制御を細かく実装している都合、
ブラウザのwindowサイズの変更に合わせて画像のサイズまで変更するのはかなり厳しい。
そこでの対応策として、
-
Stageのサイズはwindowサイズに合わせてレスポンシブに変更
-
ImageのサイズやPositionは一定に保つ
の方針で実装を試みた。
これをやったとき、実際にStageのサイズとして設定した領域よりも小さい範囲でしか画像を表示できなかったり、
Stageサイズを変更しまくると、ズーム制御がおかしくなるバグが見られた。
その後、styleのpositionを適切に設定したら上手くいくようになった。