ぱすたけ日記

日記っぽいのを書きます。

ReactとCanvasでお絵かき機能を作った話 #kyotoasterisk

プレゼンモード
再生
← / →で移動
fでフルスクリーン
escでおわる


ReactとCanvasでお絵かき機能を作った話

id:Pasta-K at 京都.なんか #2

こんにちは

  • 京都の大学生です。
  • Notaって会社でGyazoってサービスの開発のアルバイトをしています
  • 趣味としてBrowser Extensionについての観察を行っています


Gyazoの画像編集機能


四角


矢印


文字


フリーハンドペン


画像編集機能リニューアル

  • React諸々の作業も落ち着いてきたので開始
  • 図形の再編集・再配置を出来るように
  • undoとかあると便利

v1.0の実装状態

  • canvasが2つ
    • ユーザーのアクション(線を引くなど)に合わせてドンドンアップデートする用canvas
    • ユーザーのアクションが1つ完了するごとに↑のcanvasから書き込みを移植して蓄積させていく用canvas
      • こっちのcanvasが手前にあって、 mousedown -> mousemove -> mouseup を掴んでドンドン更新していく
      • ユーザーのアクション履歴の管理などもしていないので、それを管理して、そのアクション履歴に呼応してcanvasがドンドン書き換わるという感じにしたい。
  • 矢印は絶妙な線の曲がり具合などがハードコーディングされているので、そのライブラリはなんとかそのまま次でも使いたい。

今回の登場人物

  • canvas
  • ReactComponent
  • DOMEvent

canvasをReactで使うときの出来事

ReactComponentツリーの様子

  • wrapper #ミスってこいつのrenderが走ってcanvasがupdateされると困りごとが発生しがち
    • なんか # Canvasに重ねたりする要素など
    • canvasを含む要素
      • なんか # ReactCanvasとかだと図形情報を表現するReactComponent

もう少し具体的に

<Surface width={surfaceWidth} height={surfaceHeight} left={0} top={0}>
  <Layer />
  <Image style={imageStyle} src='...' />
  <Text style={textStyle}>
    京都なんか
  </Text>
  <Text>...</Text>
  <Text>...</Text>
  <Image />
  <Text>...</Text>

ドラッグでリサイズとか再配置したい

DOM Eventのハンドリングは誰が/どうやってやる?

  • canvas自身 or ラッパーのdivとかで全部まとめて取る
    • 座標から選択されたっぽい絵を探す
      • 図形の領域を常に取り出し可能な状態で管理しておく
  • 図形の位置にdivとかsvgとかなんか適当な要素を重ねて掴む

事例紹介

React Konva

Konvaという異常に気の利いたCanvasを扱うライブラリを利用したReactComponent集。

採用されている作戦

  • canvasを含む要素の子要素にRectとかArrowとかを置いておく
  • canvasを覆う同じサイズのdivがいて、それがマウスイベントをmappingする
    • なんか頑張りという感じがする

React-Canvas

canvasでスクロールに応じて、ドンドン描いていくとDOMよりもrepaintが早くてヌルヌルさせる。お絵かき機能はない。
https://flipboard.com/スマホ版で実際に使われている雰囲気っぽい?

採用されている作戦

  • canvasを含む要素の子要素にTextとかListとかを置いておく

- canvasを背景にしてて、要素の位置にaタグを重ねてクリック可能にしてる

今回の作戦

  • canvasの上に図形と同じ形のdiv OR svgを重ねてイベントをハンドリングしよう
  • 上の方のComponentのstateのshapesってとこに図形情報の配列を入れてCanvasの描画状態を管理する
    • ついでにその情報を使って図形の選択などをハンドリングするdivも描画する
    • undo / redo的な操作もstateを更新してやる

雰囲気

  • wrapper
    • Layer
      • 矢印
      • 文字
      • 矢印
      • 四角
    • canvas

canvas の状態管理

  • canvas
    • contextを操作して描画を更新する
    • contextは1つのcanvasに対して1つ
    • 一度書いた情報を再度canvasから取り出すなどは出来ない
      • 別途良い感じに管理しておく必要がある
  • 図形をちょっとズラしたいとか線の色を変えたいとかでも毎回全部描き直す必要がある

React Componentのライフサイクル

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate
  • componentWillUnmount

Canvasのアップデートをどのタイミングで挿入するか問題

  • componentをアップデートすると保持していたcanvasのcontextが死ぬ
    • 図形に重ねるイベントハンドリングのdivが多いとrepaint地獄で死ぬ
      • canvasでイベントハンドリングを全部やっていれば、この部分で悩まなくて済む
        • 図形の領域かどうかを上手く判定しまくる必要がある
          • 文字?矢印?
  • mousedown -> mousemove -> mouseup の間はrender走らせないようにsetStateを叩かずに一旦安静にしておいて、canvasのみを更新する
    • stateに入れずにstoreとか使えば良いのではと思うけど、今はcanvasだけを更新することをstoreのUpdate時に上手くお知らせしないと同じことが起きる
  • componentDidUpdateのタイミングで一斉にcanvasを全部描き直す

結果

DEMO

まとめ

  • 大体スルスルくらいは動いている状態
  • canvasのアップデートタイミングをReactのライフサイクルの中で上手くやっていく必要がある
    • ライフサイクルの気持ちになる必要がある
  • DOMは重い

心配事

  • なんかもう少し良い感じにモデル化できるのではないか
  • React使う必要があるのか

次回予告

  • textareaとcanvas同期問題
  • HiDPI環境問題
  • 拡大縮小機能でcanvasリサイズ問題