#linebootawards
の公式ハッカソンでLIFFとClovaを使った絵描き歌アプリを作っている学生チームがありました。
そこの子がハマっていたので作ってみた感じのコードになります。
今回書いてるコードは直接的にLINEのAPIとは関係ないですがLIFFのお絵描きサンプルを使ってごにょごにょしたい人が見るかなぁという想定です。
環境はmacOS Sierra、Google Chrome v69、Node.js v10.11.0です。
作ったもの
ブラウザでお絵描き出来るCanvasがあり、そこで書いたものがBase64の画像になってサーバーに送られてサーバー側で保存される。といった内容です。
ブラウザ側は3秒に一回サーバーに画像を送って、サーバー側では画像が更新されていきます。
作り方
コピペで動くと思います。
プロジェクト作成だけしておきます。myappフォルダに作っていきます。
mkdir myapp
cd myapp
npm init -y
ブラウザ(クライアント)側
myapp
フォルダにindex.htmlとserver.jsを作成しておきましょう。
まずはhtml側から。
以下の二つのライブラリを利用しています。
- signaturepad: canvasでお絵描きして画像書き出しまでを簡略化してくれる
- axios: JS向けのメジャーなHTTPクライアント。サーバーにPOSTするときに利用します
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Signature Pad demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body onselectstart="return false">
<div id="signature-pad" class="signature-pad">
<div class="signature-pad--body">
<canvas></canvas>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
const canvas = document.querySelector("canvas");
const signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgb(238,238,238)',
});
setInterval(() => {
const base64 = signaturePad.toDataURL("image/jpeg");
console.log(base64)
axios.post('/post', {
pad: base64
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
},3000)
</script>
</body>
</html>
コード解説
とりあえず動かす場合はここの解説無視でOK、↑のindex.htmlをコピペしておきましょ。
- お絵描き領域を確保
<canvas></canvas>
- canvasをお絵描き出来る状態に
const canvas = document.querySelector("canvas");
const signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgb(238,238,238)',
});
- base64変換してaxiosでサーバーに送信
const base64 = signaturePad.toDataURL("image/jpeg"); //jpegの画像としてbase64変換
console.log(base64); //base64の文字列を確認
//サーバーの`/post`に対して`{pad: <base64文字列>}`といったJSON形式で送信
axios.post('/post', {
pad: base64
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
- setIntervalで3秒(3000ミリ秒)ごと実行
setInterval(() => {
//省略
},3000)
サーバー側
server.js側です。二つのモジュールを利用します。
- express: Node.jsのWebフレームワーク
- body-parser: Expressのミドルウェア。POSTリクエストを処理する場合にほぼ必須。
npm経由でインストールします。
npm i express body-parser
'use strict';
const fs = require('fs');
const express = require('express') ;
const app = express();
const PORT = process.env.PORT || 3000;
const bodyParser = require('body-parser');
app.use(bodyParser());
app.get('/', (req, res) => res.sendFile(__dirname+'/index.html'));
app.post('/post', (req, res) => {
console.log(req.body);
console.log("postリクエストがきたよ!");
const base64 = req.body.pad.split(',')[1];
const decode = new Buffer.from(base64,'base64');
fs.writeFile('xxx.png', decode, (err) => {
if(err){
console.log(err)
}else{
console.log('saved');
}
});
});
app.listen(PORT);
console.log(`listening on *: ${PORT}`);
コード解説
これもとりあえず動かすならスルーでserver.jsをコピペだけして読み飛ばしましょ
-
body-parser
の読み込み
const bodyParser = require('body-parser');
app.use(bodyParser());
これを宣言しておくとPOSTリクエストで送られてきたBODYを処理できます。具体的にはルーティングのコールバック内でreq.body
が利用可能になります。
- index.htmlにアクセスできるようにする
app.get('/', (req, res) => res.sendFile(__dirname+'/index.html'));
/
にアクセスするとさっき書いたindex.html
の内容が呼び出されます。
- サーバーで
/post
を受け付けることができるようにする
app.post('/post', (req, res) => {
//省略
});
- base64文字列から本体だけを抽出
const base64 = req.body.pad.split(',')[1];
data:image/jpeg;base64,.........(すごーく長い文字列)
という形式で送られてくるので,
で区切った後半の内容だけを利用します。
ex: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/....(まだまだ続く)
- 抽出された画像本体のエンコード文字列をデコードして復元する
const decode = new Buffer.from(base64,'base64');
- 復元された画像データをマシンに保存する
fs.writeFile('xxx.png', decode, function(err) {
if(err){
console.log(err)
}else{
console.log('saved');
}
});
実行
node server.js
これでhttp://localhost:3000
にブラウザからアクセスするとcanvas画面が表示されてお絵描きするとserver.jsがあるフォルダにxxx.png
が作成されて3秒毎に更新されるのが確認できると思います。
まとめ
ポイントになりそうなのはbase64文字列を,
で区切るあたりとnew Buffer.from()
でデコードするあたりかなぁと思います。
それ以外はsignaturepad,axios,express,body-parserなどのライブラリを利用して楽しましょ。
よもやま
ハッカソン参加者の学生さんはどこでハマってたのかが気になってるのでなんとなく思ったこと。
PC画面をみてログをみた感じ、base64文字列がパーセントエンコードされてサーバーに送られていた様子で、それが原因だったのかなぁと思ったりしてます。クライアント側からサーバーに送る際はaxiosではなく$.ajax
を利用していてjQuery利用だった模様です。jQuery使わなくって久しいですが$.ajax
だとbase64がパーセントエンコードされちゃう現象とかあるのかなぁ(?)
あとbody-parser使わずにreq.on('data')
のchunkを取ってたのでその辺も関係しているかも(?)
気が向いたらもう少し調べてみます。