[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は面白い技術ですね。もっと色々と使ってみて、試してみたいと思います。
最後までお読み頂きまして、ありがとうございました。





