概要
HTML5の世界では<canvas>
というタグが存在し、2Dグラフィックスを描くことができます。
ただ、素の<canvas>
は操作処理が手続き型なので、宣言的に記述できるライブラリが欲しいと思っていました。
そこで目をつけたのがKonva Framework。こちらは2Dグラフィックスを描くためのJavaScriptライブラリなのですが、それをReact風に宣言的に使えるreact-konvaというライブラリもあります。なので今回は、react-konvaのざっくりとした使用感について解説します。
導入
# npm の場合
npm install react-konva konva --save
# yarnの場合
yarn add react-konva konva
概念
このライブラリで宣言的に記述できるのは、<Stage>
タグ、<Layer>
タグ、<Group>
タグ、<Rect>
・<Text>
・<Image>
などの各種描画用タグです。階層構造としては次の通り。
Stage
|
+------+------+
| |
Layer Layer
| |
+-----+-----+ Shape
| |
Group Group
| |
+ +---+---+
| | |
Shape Group Shape
|
+
|
Shape
(出典:https://konvajs.org/docs/overview.html)
-
<Stage>
タグ……描画の土台となるタグ。おおよそ<canvas>
タグに相当。<canvas>
の表示サイズや表示スケールを決めたり、これより内側の描画をtoDataUrl()
メソッドで画像化したりするのに使える -
<Layer>
タグ……名前の通り、描画レイヤーとして機能するタグ。z-index
、つまり表示順をこの単位で管理できる -
<Group>
タグ……こちらは必須ではないが、描画用タグをグルーピングするために使う。グループ単位で描画を拡大・縮小・回転・移動させることができる - 描画用タグ……四角形を描画する
<Rect>
、テキストを描画する<Text>
、画像を描画する<Image>
などのタグ。塗りつぶしの方法や透明度など、細かな設定も可能
より詳しい概要については、Konva自体の概要説明ページを読むと良いでしょう。
基本的な使い方
まずは、500x500ピクセルの<canvas>
内に、左上座標が(100, 100)で、大きさが300x200な赤色の長方形を描いてみます。
見やすさの問題から、その長方形よりも先に、外枠として辺だけの長方形も黒線で描画しておきます。
ちなみに、いずれの描画用タグからも塗りつぶされていない箇所は透明色になっています。
import { Layer, Rect, Stage } from "react-konva";
// (中略)
return <Stage width={500} height={500}>
<Layer>
<Rect stroke='black' strokeWidth={4} x={5} y={5} width={490} height={490} />
<Rect fill='red' x={100} y={100} width={300} height={200} />
</Layer>
</Stage>;
真円を描く場合は<Circle>
、楕円を描く場合は<Ellipse>
、(折れ曲がりも含めた)直線を引く場合は<Line>
を使用します。
他にも様々な描画用タグはありますが、使い方はKonvaのドキュメントを読めば察せられるかと。
後、opacity
属性は透明度を決めるもので、0が完全透明・1が無透明です。
<Stage width={500} height={500}>
<Layer>
<Rect stroke='black' strokeWidth={4} x={5} y={5} width={490} height={490} />
<Rect fill='red' x={100} y={100} width={300} height={200} />
<Circle fill='blue' x={100} y={100} radius={50} opacity={0.5} />
<Ellipse fill='green' x={250} y={300} radiusX={100} radiusY={180} />
<Line points={[400, 50, 300, 150, 150, 170, 300, 50]} stroke='purple' strokeWidth={15} />
</Layer>
</Stage>
面白いのが<Text>
タグ。純正<canvas>
におけるテキスト描画用関数(fillTextメソッドやstrokeTextメソッド)では自動で折り返してくれないのに対し、<Text>
タグでは(最大横幅を設定すると)自動で折り返してくれます。また、テキスト内に改行を入れると、それに従って改行を入れてくれます。
<Stage width={500} height={500}>
<Layer>
<Rect stroke='black' strokeWidth={4} x={5} y={5} width={490} height={490} />
<Text text={text} x={50} y={50} width={400} fontSize={40} fontFamily={'Calibri'} fill='black' align='left' />
</Layer>
</Stage>
画像読み込みも可能です。サンプル画像は貼りませんが、指定したサイズにリサイズした上で貼ってくれます。
また、image
属性に渡せるものは、Canvas APIにおける画像描画で使用できるそれと同様です。つまり、TypeScriptで言えば、
-
HTMLImageElement
型……<img>
タグ自体のDOM -
SVGImageElement
型……<svg>
タグ自体のDOM -
HTMLVideoElement
型……<video>
タグ自体のDOM -
HTMLCanvasElement
型……<canvas>
タグ自体のDOM -
ImageBitmap
型……事前に描画のための処理を済ませた画像データ。Serializable
なので保存しやすい -
OffscreenCanvas
型……名前の通り、画面に描画することを考慮してない<canvas>
。データキャッシュ用に利用できる
が利用可能です。
<Stage width={500} height={500}>
<Layer>
<Image image={imageData} x={50} y={50} width={300} height={300} />
</Layer>
</Stage>
座標系について
<Stage>
タグに設定するwidth
属性やheight
属性はピクセル単位ですが、実は、<Layer>
タグ以下に配置した各種描画タグで使うx
属性やy
属性などで設定するピクセル単位と微妙に異なります。以下では、両者をそれぞれ、次のように言い分けます。
-
見かけ上のピクセル……
<Stage>
タグに設定するwidth
属性やheight
属性に設定するもの -
描画上のピクセル……各種描画タグで使うに設定する
x
・y
・width
・height
属性などに設定するもの
見かけ上のピクセルは<canvas>
自体の表示サイズに関わります。一方、描画上のピクセルは、<Stage>
タグに別途設定したscale
属性の値に従い、実際に描画される際のサイズが変わります。例えば、次のように書いたとしましょう。
const scaleX = 0.5;
const scaleY = 0.4;
return <Stage scale={{x: scaleX, y: scaleY}} width={500} height={500}>
<Layer>
<Rect stroke='black' strokeWidth={4} x={5} y={5} width={490} height={490} />
<Rect fill='red' x={100} y={100} width={300} height={200} />
</Layer>
</Stage>;
ここで、<Rect>
がタグどのように描画されるかを考えます。<Stage>
タグにscale
属性を設定した影響で、x
属性やwidth
属性はscaleX
倍された大きさとして描画されます(y
属性などはscaleY
倍)。
しかし、<canvas>
自体は<Stage>
タグのwidth
属性・height
属性の値に従うので、結局、「500x500ピクセルの画像の中に、左上座標(50, 40)から大きさ150x80の赤い長方形が描画」されます。
<Stage>
自体の大きさもscale
に合わせたいなーって時は、<Stage>
自体のwidth
・height
属性もスケーリングする必要があります。ただし、その場合でも、描画上のピクセルに対応する、各種描画タグに、いちいちscaleX
だのscaleY
だのを使う必要はありません。
const scaleX = 0.5;
const scaleY = 0.4;
return <Stage scale={{x: scaleX, y: scaleY}} width={500 * scaleX} height={500 * scaleY}>
<Layer>
<Rect stroke='black' strokeWidth={4} x={5} y={5} width={490} height={490} />
<Rect fill='red' x={100} y={100} width={300} height={200} />
</Layer>
</Stage>;
画像保存について
<canvas>
で描画したものを画像データ化したいことはよくあると思います。<Stage>
においてもそういった場合の処理は提供されていますが、React的にはuseRef
を使うのがベターでしょう。具体的にはこんな感じ。
import React, { useRef } from "react";
import { Stage as StageType } from 'konva/types/Stage';
const stageRef = useRef<StageType>(null);
const process = () => {
const temp = stageRef.current;
// stageRefの中身(temp )がnullな可能性を考慮してチェック
if (temp !== null) {
// dataUrlに、画像データがdata URL(MIME Type + base64文字列)形式で書き込まれる。
// toDataURLの引数を変更すれば、PNG以外の画像形式への変換も可能
const dataUrl = temp.toDataURL();
// これ以降、dataUrlを使った操作(画像保存、画像加工等)を行う
}
};
return <Stage ref={stageRef} width={500} height={500}>
<Layer>
<Rect stroke='black' strokeWidth={4} x={5} y={5} width={490} height={490} />
<Rect fill='red' x={100} y={100} width={300} height={200} />
</Layer>
</Stage>;
興味深いのは、toDataURL
メソッド内の引数にpixelRatio
というのがあることです。例えばtemp.toDataURL({pixelRatio: 2})
とすれば、<Stage>
のwidth
が100
、height
が200
だった場合、200x400ピクセルの画像として書き出されます。
つまり、例えば、「<canvas>
をWebページ上では小さく表示するが、ダウンロードする際は大きい状態にする」ことが簡単にできます。