Next.js + react-konvaを使ってみた
こんにちは! takjinです。
今回はNext.js + react-konvaを使う機会がありましたので、導入から描画、画像データを保存するところまで、紹介していきたいと思います。
実行環境はこちらです
konva: 8.3.9
next: 12.1.6
react: 18.1.0
react-konva: 18.1.1
Next.jsとreact-konvaのセットアップ
Next.jsのプロジェクトを作成し、react-konvaをインストールしていきます
// Next.jsのセットアップ
$ npx create-next-app@latest
// Next.jsでtypescriptを使いたい場合
$ npx create-next-app@latest --typescript
// react-konvaのインストール
$ npm install react-konva konva
セットアップが完了するとNext.jsのプロジェクトが作成されます。
デフォルトではプロジェクトルート直下にpagesが作られますが、今回はsrcディレクトリを作ってその中に作成しています(お好みでどうぞ
src/
├──pages
├──components
tsconfig.js
{
"compilerOptions": {
"sourceRoot": "src"
}
}
Konvaで描画してみる
まずは、Konvaで用意されているCircleを使って表示をしてみます
src/pages/canvas.tsx
import { FC } from 'react'
import dynamic from 'next/dynamic'
// react-konvaを使用しているコンポーネントはdynamic importを利用する
const StageComponent = dynamic(() => import('../components/StageComponent'), { ssr: false })
// これだと、エラーになる Error: Must use import to load ES Module...
// import StageComponent from '../components/StageComponent'
const CanvasPage: FC = () => {
return (
<StageComponent />
)
}
export default Canvas
src/components/StageComponent.tsx
import { FC } from 'react'
import { Stage, Layer, Circle } from 'react-konva'
const StageComponent: FC = () => {
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Circle x={100} y={100} radius={50} fill="green" />
</Layer>
</Stage>
)
}
export default Canvas
Stage > Layer > Shape という構成で描画していきます
https://konvajs.org/docs/overview.html
Next.jsはデフォルトではpre-rendering(SSR)ですが、react-konvaはCSR前提のため
dynamic importを使ってCSRで実行させるようにします
https://github.com/konvajs/react-konva/issues/588
https://github.com/vercel/next.js/issues/25454#issuecomment-862571514
任意の画像を表示したい場合はImage
を使います
// use-imageを使う場合
// https://github.com/konvajs/use-image
import { Stage, Layer, Image } from 'react-konva'
import useImage from 'use-image'
const [image] = useImage(url)
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Image image={image} />
</Layer>
</Stage>
)
// ライブラリ使わない場合
import { useEffect, useState } from 'react'
import { Stage, Layer, Image as RKImage } from 'react-konva'
const [image, setImage] = useState()
useEffect(() => {
const newImage = new Image
newImage.src = url
newImage.addEventListener('load', () => setImage(newImage))
return () =>
newImage.removeEventListener('load', () => setImage(newImage))
}, [url])
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<RKImage image={image} />
</Layer>
</Stage>
)
手書きの線を描いてみる
準備が整ったので、下記のようなコンポーネントを用意(コードは簡略化したイメージになります)して手書きの線を描画していきます。
/src/components/StageComponent.tsx
import { FC, useState } from 'react'
import { Stage, Layer, Line } from 'react-konva'
type LineType = {
points: number[]
}
const StageComponent: FC = () => {
const [drawLine, setDrawLine] = useState<LineType>()
const [lines, setLines] = useState<LineType[]>([])
// 手書き開始
const handleOnMouseDown = (e) => {
const position = e.target.getStage().getPointerPosition()
const { x, y } = position
setDrawLine({
points: [x, y]
})
}
// 手書き中
const handleOnMouseMove = (e) => {
if (!drawLine.points) return
const position = e.target.getStage().getPointerPosition()
const { x, y } = position
setDrawLine({
points: [...drawingPath.points, x, y]
})
}
// 手書き終了
const handleMouseUp = () => {
if (!drawLine.points) return
setDrawLine(undefined)
setLines([
...lines,
{ points: drawLine.points }
])
}
return (
<Stage
width={window.innerWidth}
height={window.innerHeight}
onMouseDown={handleOnMouseDown}
onMouseMove={handleOnMouseMove}
onMouseUp={handleMouseUp}
>
<Layer>
{[...lines, drawLine].map((line, index) => (
<Line
key={index}
points={line.points}
fill="black"
stroke="black"
lineCap="round"
draggable={true}
/>
))}
</Layer>
</Stage>
)
}
export default CanvasStage
handleOnMouseDown
、handleOnMouseMove
で内で使っているgetPointerPosition()
で取得した値をLine
のpoints
に入れることで線を描画することができます。
draggable={true}
にすると描画した線をドラッグして任意の場所に移動させることも可能です。他にも様々なプロパティが用意されているので用途に応じて使うと良いでしょう。
画像データ化して保存する
描画したcanvasを画像データとして保存してみます。
KonvaにtoDataUrlメソッドが提供されているのでこちらを使っていきます。
const stageRef = useRef<Konva.Stage>(null)
const handleOnSubmit = () => {
const temp = stageRef.current
// データURL形式で値を取得できる
const result = temp.toDataUrl({
mimeType,
pixelRatio,
width,
height,
x,
y
})
// resultを使って、ここから先は任意の保存処理など...
}
<>
<Button
onSubmit={handleOnSubmit}
/>
<Stage
ref={stageRef}
...
>
<Layer>
<Image ... />
<Line ... />
<Line ... />
</Layer>
</Stage>
</>
Stage
にref
を指定してref.current.toDataUrl()
でデータURL形式で値を取得するようにしてみました。
toDataUrlのオプションにあるpixelRatio
はStage
の縦横サイズに対するアスペクト比を指定できます。また、x
、y
、width
、height
の指定ができるので、トリミングした画像データを生成したい時などに使うと便利でしょう。
Next.js + react-konvaの紹介は以上になります。それでは、皆さん素敵な手書きライフを!
株式会社アルダグラムのTech Blogです。 世界中のノンデスクワーク業界における現場の生産性アップを実現する現場DXサービス「KANNA」を開発しています。 採用情報はこちら: herp.careers/v1/aldagram0508/
Discussion