Help us understand the problem. What is going on with this article?

FirebaseとReact Nativeでオンラインホワイトボードを作る

この記事は Firebase Advent Calendar 2020 の4日目の記事となります。

FirebaseとReact Nativeを用いて、リアルタイムで手書きを共有できるオンラインホワイトボードを作成したので、その設計や実装方法をご紹介します。

861e3469b84c0b1f76eb9dd3ef90aa09.gif

モチベーション

オンライン会議中にiPadの手書きをサクッと共有したかったことです。
類似のサービスはいくつかあったけど、閲覧者側にログインが必要だったり、機能が過多だったりしたので、もっとシンプルに共有できるアプリが欲しいと思いました。

要求仕様

  • iPadの手書きをURLで共有できる
  • 共有された側はブラウザ or アプリで手書きを閲覧できる
  • 手書きの更新はリアルタイムで共有される
  • 複数人で同時編集することができる

設計の検討

リアルタイムでデータを共有するためにFireStoreを使うのがよさそうです。

難しいのは双方向で編集するために、手書き情報をどのように保持するかです。

手書き情報を一枚の画像として保持すると、同時に複数の人が線を書いたときのデータの合体が困難になります。

そこで、線の一本づつをdocumentとして保持するようにしました。

そのためには線の情報を、FireStoreに保存できるデータとして表現する必要があります。そこで今回はsvgのpath情報として線の情報を保持することとしました。
svgを使うと線を以下のような数値にシリアライズして表現できます。

285,156.5 288,156.5 294.5,156.5 301,156.5 305,156.5 307.5,156.5 308.5,157.5

FireStoreの構成

一枚のホワイトボードをboardドキュメントで管理します。

boardの下に線(polyline)のサブコレクションを持たせます。

Untitled (11).png

アプリ側ではpolylinesコレクションをonSnapShotで監視することで、リアルタイムのデータ更新を実現します。

チャットアプリがメッセージをやりとりする要領で、このアプリでは線(polyline)をやり取りするイメージです。

Untitled(1) (5).png

React Native側の実装

React Nativeで手書きをどう実現するか考えたいと思います。

線の表示

React Nativeではsvg情報を描画するためにreact-native-svgというコンポーネントが提供されているので、これを利用します。

背景の四角形と、線分を1本を描画するコードは以下になります。

スクリーンショット 2020-11-29 8.51.35.png

import Svg, { Polyline, Rect } from "react-native-svg";

export const BoardScreen () => {

  return (
      <Svg height={height} width={width} viewBox={`0 0 ${width} ${height}`}>
        {/* 背景 */}
        <Rect
          x={0}
          y={0}
          width={width}
          height={height}
          stroke="#000"
          strokeWidth="1"
          fill="#fff"
        />
        {/* 線 */}
        <Polyline
          points="99.5,151.5 104.5,151.5 119,153.5 135,156 167.5,161 194.5,164.5 218,167.5 236,171 244.5,174.5 249,176 255.5,180 260.5,183.5 267,185.5 271,187.5 274.5,189 276.5,190 277.5,190.5 279,192 279.5,192 281.5,193 283,195.5"
          fill="none"
          stroke="#f00"
          strokeWidth="3"
        />
      </Svg>
  );
}

タッチの検出

お馴染みのViewコンポーネントでタッチを検出できます。

1b8cde535391233a750a0315b799b041.gif

export type Point = {
  x: number;
  y: number;
};


export const BoardScreen () => {
  // 描画中の線分の点情報を配列で保持する
  const [points, setPoints] = useState<Point[]>([])

  const onTouchMove = (event: GestureResponderEvent) => {
    const { locationX, locationY, touches } = event.nativeEvent;
    if (touches.length === 1) {
      // 描画中の線分の情報を追加する
      setPoints([...points, { x: locationX, y: locationY }]);
    }
  }

  const onTouchEnd = async (event: GestureResponderEvent) => {
      if (points.length > 0) {
        /* TODO: ここでFireStoreにpolylineドキュメントを追加する処理 */

        // 描画中の線分の情報をクリア
        setPoints([]);
      }
    } 
  };

  return (
     <View
        onTouchMove={onTouchMove}
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}
        onTouchCancel={onTouchCancel}
      >
        <Svg>
          /* 略 */
        </Svg>
      </View>
  );
}

実際には他にも細々したことをやっていますが、基本の部分は上記のようなコードで実現できます。

課題

線の描画の度にドキュメントを作成するので、通信量がそこそこ多くなります。

費用のことを考えるとFireStoreでなくてRealTime Databaseのほうが良いのかしれません。
FireStoreは書き込み/読み込みの度に課金されますが、RealTime Databaseはダウンロード/アップロードの容量ごとに課金されます。

今回の場合小さいデータを頻繁に読み書きするので、もしかするとRealTime Databaseのほうが費用的には安くなるのかもしれません。

ただデータの取扱いはFireStoreのほうがやりやすいし悩みどころです…

できたもの

いちおうストアで公開しています。

「air board - ホワイトボードをサクッとシェア」
https://apps.apple.com/jp/app/id1524182843

(余談)

先述のような「iPadの手書きをサクッと共有したい」というモチベーションで開発をしましたが、開発してからGoogleのJamBoardというサービスを知りました。
限定URLで簡単に手書きを共有できたりします。

正直JamBoardのほうがイケてます(笑)
Googleのサービスでこんなのがあったとは…灯台下暗し。

takahi5
React Native/React/Expo/Rails/ UdemyでReact Native講座やってます。クーポンこちら→ https://blog.takahi5.com/udemy-coupon/
https://blog.takahi5.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away