LoginSignup
38
29

More than 3 years have passed since last update.

react-konvaライブラリが使って便利だったので使用法などをメモ

Posted at

概要

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>;

image.png

真円を描く場合は<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>

image.png

面白いのが<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.png

画像読み込みも可能です。サンプル画像は貼りませんが、指定したサイズにリサイズした上で貼ってくれます。
また、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属性に設定するもの
  • 描画上のピクセル……各種描画タグで使うに設定するxywidthheight属性などに設定するもの

見かけ上のピクセルは<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の赤い長方形が描画」されます。

image.png

<Stage>自体の大きさもscaleに合わせたいなーって時は、<Stage>自体のwidthheight属性もスケーリングする必要があります。ただし、その場合でも、描画上のピクセルに対応する、各種描画タグに、いちいち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>;

image.png

画像保存について

<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>width100height200だった場合、200x400ピクセルの画像として書き出されます。
つまり、例えば、「<canvas>をWebページ上では小さく表示するが、ダウンロードする際は大きい状態にする」ことが簡単にできます。

38
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
29