[node] socket.IOとHTML5 Canvasを用いた手書きチャットアプリを作ってみた
node.jsとsocket.IOとHTML5のCanvasを用いて、
手書きチャットアプリを作ってみました。
socket.ioを用いるとリアルタイムWebが実現でき、Canvasのお絵描き機能と合わせることで、 Canvasでの描画を自分のブラウザと他人のブラウザとで、 同じ内容をリアルタイムで描画出来るサンプルアプリです。
今日は、その機能と実装内容を紹介したいと思います。
YouTubeに動画を載せてみました。動画上では、同一PC上での動きですが、それぞれのブラウザは 独立して動いている状態で、ローカルマシンにnode.jsのサーバーがある環境となります。
表示されない場合にはこちらから→http://youtu.be/aJam4zco3YQ
最初にnode.jsを起動して、色々なブラウザからnodeサーバーにつないで、お絵描きをします。
お絵描きをした内容は、接続中の他全てのブラウザに連携され、リアルタイムに描画されます。
それぞれの中身もそれほどコード量は多くなく、実装は簡単な感じです。
サンプルアプリなので、色々なところでコンソールにデバッグ情報を出漁しています。
また、今回は描画情報を逐次送っていますが、パフォーマンスチューニングの観点から、ある程度纏めてから 送るのも良いかもしれません。 (というか、サーバーがローカル以外なら、通信時間が馬鹿にならないので、チューニングは必須となりそうです)
socket.IOは面白い技術ですね。もっと色々と使ってみて、試してみたいと思います。
最後までお読み頂きまして、ありがとうございました。
socket.ioを用いるとリアルタイムWebが実現でき、Canvasのお絵描き機能と合わせることで、 Canvasでの描画を自分のブラウザと他人のブラウザとで、 同じ内容をリアルタイムで描画出来るサンプルアプリです。
今日は、その機能と実装内容を紹介したいと思います。
手書きチャットアプリの機能
手書きチャットアプリでは、自分のブラウザ上のCanvasに手書きで文字を描くと、 リアルタイムに接続している他のユーザーの画面にも描画されるという動きをします。YouTubeに動画を載せてみました。動画上では、同一PC上での動きですが、それぞれのブラウザは 独立して動いている状態で、ローカルマシンにnode.jsのサーバーがある環境となります。
表示されない場合にはこちらから→http://youtu.be/aJam4zco3YQ
最初にnode.jsを起動して、色々なブラウザからnodeサーバーにつないで、お絵描きをします。
お絵描きをした内容は、接続中の他全てのブラウザに連携され、リアルタイムに描画されます。
実装内容の紹介
続いて、実装内容を紹介します。実装は、サーバー用のファイル1つと、 クライアント側のファイル1つの計2つです。それぞれの中身もそれほどコード量は多くなく、実装は簡単な感じです。
サーバーサイドの実装
サーバーサイドでは、node.jsでのWebアプリケーションサーバー構築と、 socket.IOを用いたWebSocketプロトコルでの通信を実装しています。// 必要なモジュールを読み込みます。 var http = require("http"); var socketIO = require("socket.io"); var fs = require("fs"); // node.jsでWebServerを作ります。 // アクセスされたら、クライアントに表示するsyncCanvas.htmlを返します。 var server = http.createServer(function (req, res) { res.writeHead(200, {"Content-Type":"text/html"}); var output = fs.readFileSync("./syncCanvas.html", "utf-8"); res.end(output); }); server.listen(8080); // socket.IOを用いたリアルタイムWebを実装します。 var io = socketIO.listen(server); // 接続されたら、connected!とコンソールにメッセージを表示します。 io.sockets.on("connection", function (socket) { console.log("connected"); // 描画情報がクライアントから渡されたら、接続中の他ユーザーへ // broadcastで描画情報を送ります。 // ちなみに、最近のsocket.IOでは、イベント名(以下だとdraw)は // 自由にネーミング出来るようになったようです。便利!! socket.on("draw", function (data) { console.log(data); socket.broadcast.emit("draw", data); }); // 色変更情報がクライアントからきたら、 // 他ユーザーへ変更後の色を通知します。 socket.on("color", function (color) { console.log(color); socket.broadcast.emit("color", color); }); // 線の太さの変更情報がクライアントからきたら、 // 他ユーザーへ変更後の線の太さを通知します。 socket.on("lineWidth", function (width) { console.log(width); socket.broadcast.emit("lineWidth", width); }); });
クライアント側の実装
クライアント側の実装のメインは、JavaScriptです。 Canvasへの描画処理と、socket.IOを用いる処理が記載されているので、 サーバーサイドよりもコード量は多くなっています。<html> <head> <meta charset="utf-8"> <title>Sync Canvas</title> <style type="text/css"> * {margin:0px; padding:0px;} body {width:500px;} .title {padding:10px;} .main {padding-left:10px; margin:auto;} .toolbar {} .toolbar li {width:44px; height:44px; margin:auto;list-style-type: none; border:1px solid #ccc; border-radius:6px; margin:10px; display:block; float:left;} #black {background-color:black;} #red {background-color:red;} #green {background-color:green;} #blue {background-color:blue;} #small {text-align:center; line-height:44px; font-size:100%;} #middle {text-align:center; line-height:44px; font-size:200%;} #large {text-align:center; line-height:44px; font-size:400%;} .canvas {width:450px; height:400px; border:1px solid #ccc;} .canvas canvas {width:450px; height:400px;} </style> </head> <body> <!-- Header --> <h1 class="title">Sync Canvas</h1> <!-- Main --> <div class="main"> <div class="toolbar"> <ul> <li id="black"> </li> <li id="blue"></li> <li id="red"></li> <li id="green"></li> <li id="small">S</li> <li id="middle">M</li> <li id="large">L</li> </ul> </div> <div style="clear:left;"></div> <div class="canvas"><canvas id="myCanvas"></canvas></div> </div> <!-- ここでsocket.IOのライブラリを読み込む --> <script src="/socket.io/socket.io.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script> <script type="text/javascript"> window.addEventListener("load", function () { // サーバーサイドのsocket.IOに接続する // 接続出来たら、サーバー側のコンソールにconnected!と表示される var socket = io.connect("/"); // Canvas描画に必要な変数を定義する var canvas = document.getElementById("myCanvas"); var c = canvas.getContext("2d"); var w = 450; var h = 400; var drawing = false; var oldPos; // Canvasを初期化する canvas.width = w; canvas.height = h; c.strokeStyle = "#000000"; c.lineWidth = 5; c.lineJoin = "round"; c.lineCap = "round"; // Canvas上の座標を計算する為の関数たち function scrollX(){ return document.documentElement.scrollLeft || document.body.scrollLeft; } function scrollY(){ return document.documentElement.scrollTop || document.body.scrollTop; } function getPos (event) { var mouseX = event.clientX - $(canvas).position().left + scrollX(); var mouseY = event.clientY - $(canvas).position().top + scrollY(); return {x:mouseX, y:mouseY}; } function getPosT (event) { var mouseX = event.touches[0].clientX - $(canvas).position().left + scrollX(); var mouseY = event.touches[0].clientY - $(canvas).position().top + scrollY(); return {x:mouseX, y:mouseY}; } // ここからは、Canvasに描画する為の処理 canvas.addEventListener("mousedown", function (event) { console.log("mousedown"); drawing = true; oldPos = getPos(event); }, false); canvas.addEventListener("mouseup", function () { console.log("mouseup"); drawing = false; }, false); canvas.addEventListener("mousemove", function (event) { var pos = getPos(event); console.log("mousemove : x=" + pos.x + ", y=" + pos.y + ", drawing=" + drawing); if (drawing) { c.beginPath(); c.moveTo(oldPos.x, oldPos.y); c.lineTo(pos.x, pos.y); c.stroke(); c.closePath(); // socket.IOサーバーに、 // どの点からどの点までを描画するかをの情報を送付する socket.emit("draw", {before:oldPos, after:pos}); oldPos = pos; } }, false); canvas.addEventListener("mouseout", function () { console.log("mouseout"); drawing = false; }, false); // 色や太さを選択した場合の処理 // 選択した結果を、Canvasに設定して、 // socket.IOサーバーにも送付している $("#black").click(function () {c.strokeStyle = "black";socket.emit("color", "black");}); $("#blue").click(function () {c.strokeStyle = "blue";socket.emit("color", "blue");}); $("#red").click(function () {c.strokeStyle = "red";socket.emit("color", "red");}); $("#green").click(function () {c.strokeStyle = "green";socket.emit("color", "green");}); $("#small").click(function () {c.lineWidth = 5;socket.emit("lineWidth", 5);}); $("#middle").click(function () {c.lineWidth = 10;socket.emit("lineWidth", 10);}); $("#large").click(function () {c.lineWidth = 20;socket.emit("lineWidth", 20);}); // socket.IOサーバーから描画情報を受け取った場合の処理 // 受け取った情報を元に、Canvasに描画を行う socket.on("draw", function (data) { console.log("on draw : " + data); c.beginPath(); c.moveTo(data.before.x, data.before.y); c.lineTo(data.after.x, data.after.y); c.stroke(); c.closePath(); }); // socket.IOサーバーから色情報を受け取った場合の処理 // Canvasに色を設定している socket.on("color", function (data) { console.log("on color : " + data); c.strokeStyle = data; }); // socket.IOサーバーから線の太さ情報を受け取った場合の処理 // Canvasに線の太さを設定している socket.on("lineWidth", function (data) { console.log("on lineWidth : " + data); c.lineWidth = data; }); }, false); </script> </body> </html>
サンプルアプリなので、色々なところでコンソールにデバッグ情報を出漁しています。
また、今回は描画情報を逐次送っていますが、パフォーマンスチューニングの観点から、ある程度纏めてから 送るのも良いかもしれません。 (というか、サーバーがローカル以外なら、通信時間が馬鹿にならないので、チューニングは必須となりそうです)
最後に
本当はこのデモアプリを触れる状態にしようと、Amazon EC2上に乗せようと思ったのですが、 起動しないというエラーが。。socket.IOは面白い技術ですね。もっと色々と使ってみて、試してみたいと思います。
最後までお読み頂きまして、ありがとうございました。