「LINE 手描きスタンプ」「お絵描き共有サービス」「お絵描き Slack」などのお絵描き系(そんな系あるのか?)ウェブアプリを複数開発・運用しています。

これらのウェブアプリで使っている「お絵描きパネル」 UI 部分の作りはほぼ共通です。細かいことを言い出すと i18n 対応の有無とか、履歴呼び出しの有無とか、各アプリ毎に固有の情報を保存したりしなかったりしているので UI 部分含めて全くの共通ではないのですが、でも共通パーツが多いのも事実です。

今後も新しいアプリでこの「お絵描きパネル」を使うことも何度かあるだろう、というわけで、今後のアプリ開発を楽にする目的でパネル部分のモジュール化を地味に続けていました。とりあえず公開してもいいかな、というレベルになったので公開します。

ソースコードはこちらです:
https://github.com/dotnsf/doodlejs

実際に動くサンプルはこちら(スマホか PC のウェブブラウザでアクセスしてください):
https://dotnsf.github.io/doodlejs/

2021052501


実際の UI 含めた挙動は動くサンプルのページで確認してください。2021/05/24 時点では以下のような機能を持っています:
・PC のマウスやトラックボール/Windows タッチパネル/スマホのタッチでの描画に対応
・i18n は無し(画面は日本語のみ)
・線の色、太さを指定して描画する。指定した色を背景色に指定することもできる。
・アンドゥ・リドゥ・リセット可
・「送信」ボタンを押すと、描画内容を画像化し、エンコードした結果を console.log() で書き出す(スマホだと確認できません、ごめんなさい)
・「送信」ボタンを押した時の挙動はカスタマイズ化。実際に描画データをサーバー側へ送ってバックエンドに保存する、といったこともできる(ここまで含めてサンプルコードあり)


↑特に最後のカスタマイズ機能の目処がたったので公開を決断しました。サンプルのページだけでもお絵描きの描画は体験できます。が、実際にこのモジュールを組み込んで作るアプリでは描いた絵を保存する機能も必須だと思っています。一方、Github ページでサンプル公開しているとその部分の実現が難しく、保存するようなカスタマイズができる必要性も考慮した上で試行錯誤した結果、まあまあいい感じのバランスが取れた状態で公開しています。


【使い方】
自分のウェブアプリに組み込んで使う場合は以下の作業を行ってください:

①ソースコードを git clone またはダウンロード

フロントエンドで最低限必要なファイルは doodle.js ファイルと、サンプルページである index.html の2つです。

バックエンド側のカスタマイズも行う場合は、そのサンプルとなっている app.js ファイルも必要です。

②サンプルを元にウェブページを作成

フロントエンドのサンプル UI となる index.html を参考に(あるいはこのまま使って)ウェブページを用意します。同ウェブページには以下の要素が含まれている必要があります(サンプルの index.html にはすべて含まれています):

・お絵描き用のキャンバスを含むことになる <div id="cdiv"></div>
・色選択パーツの <select id="select_color"> ~ </select>
・線の太さ選択パーツの <select id="select_linewidth"> ~ </select>
・背景色変更ボタン <button onClick="setBG();"> ~ </button>
・アンドゥボタン <button onClick="undo();"> ~ </button>
・リドゥボタン <button onClick="redo();"> ~ </button>
・リセットボタン <button onClick="resetCanvas();"> ~ </button>
・送信ボタン <button onClick="sendCanvas();"> ~ </button>

ウェブページが用意できたら、画面ロード後にそれぞれの id 値を指定して以下の JavaScript を実行します(サンプルの index.html には既に含まれています):
$( function(){
  $('#cdiv').doodlejs({
    select_color: 'select_color',
    select_linewidth: 'select_linewidth',
    undo_btn: 'undo_btn',
    redo_btn: 'redo_btn',
    setbg_btn: 'setbg_btn'
  });
});

これで用意されたパーツ要素を使って、指定された <div id="cdiv"></div> 内に連動して動く Canvas が生成されます。

③「送信」ボタンのカスタマイズ

お絵描き後に「送信」ボタンを押した時の挙動をカスタマイズできます。上記サンプルでは単にお絵描き内容を画像化→エンコードして、結果のテキストを console.log() で出力しているだけですが、バックエンドに送信するようなケースを想定してカスタマイズできるようにしています。

送信ボタンをクリックした時の挙動をカスタマイズするには DOODLEJS.prototype.postCanvas() 関数を上書きします。この関数はお絵描き内容を画像化(png 化)したデータを引数に実行されます。プロトタイプでは以下の内容になっているので、その画像をエンコードして console.log() に出力しています:
(カスタマイズ前の処理)
DOODLEJS.prototype.postCanvas = function( png ){
  console.log( 'png', png );
};

例えば画像データをサーバーに送る場合は以下のような内容にプロトタイプを上書きします:
DOODLEJS.prototype.postCanvas = function( png ){
  //. バイナリ変換
  var bin = atob( png );
  var buffer = new Uint8Array( bin.length );
  for( var i = 0; i < bin.length; i ++ ){
    buffer[i] = bin.charCodeAt( i );
  }
  var blob = new Blob( [buffer.buffer], {
    type: 'image/png'
  });

  //. フォームにして送信
  console.log( 'Sending data... : ' + blob.size );
  var formData = new FormData();
  formData.append( 'image', blob ); 
  formData.append( 'timestamp', ( new Date() ).getTime() );

  $.ajax({
    type: 'POST',
    url: '/image',
    data: formData,
    contentType: false,
    processData: false,
    success: function( data, dataType ){
      console.log( data );
    },
    error: function( jqXHR, textStatus, errorThrown ){
      console.log( textStatus + ': ' + errorThrown );
    }
  });
};

これで画像データが POST /image という REST API でポストされることになります。後はバックエンド側でこのルーティングを処理するような REST API をバックエンドに用意し、送られてくる画像データを解析するなり、保存するなり、、といった処理を実装することになります(以下は app.js に含まれるサンプルで、特に保存せずに送信された画像の情報を返すだけの内容です):
app.post( '/image', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );

  if( req.file ){
    var imgpath = req.file.path;
    var imgtype = req.file.mimetype;
    var imgsize = req.file.size;
    //var imgfilename = req.file.filename;
    //var filename = req.file.originalname;

    var timestamp = parseInt( req.body.timestamp );

    var img = fs.readFileSync( imgpath );
    var img64 = new Buffer( img ).toString( 'base64' );
    fs.unlink( imgpath, function( err ){} );

    var params = {
      path: imgpath,
      type: imgtype,
      size: imgsize,
      timestamp: timestamp
    };
    console.log( params );
    var p = JSON.stringify( params, null, 2 );
    res.write( p );
    res.end();
  }else{
    res.status( 400 );
    res.write( JSON.stringify( { status: false, error: 'not initialized.' } ) );
    res.end();
  }
});

ウェブ画面内にマウスや指でお絵描きできるパーツ部品を組み込んで、送信後のデータ処理までカスタマイズするのに便利だと思っています。よかったら使ってください。