HTML5のWebsocketを使ったお絵かきチャット

HTML5のWebsocketを使ったお絵かきチャット

Posted at February 13,2012 12:03 AM
Tag:[Chat, HTML5, WebSocket]

HTML5のWebsocket(Node.js+Socket.IO)を使って、お絵かきチャットが行えるサンプルを作ってみました。Node.js+Socket.IOについては、下記のエントリーを参照してください。

Windows+Node.js+Socket.IO

環境を作ったのは自分のPCのためリアルでお見せできないのが残念ですが、動画をキャプチャしたのでそちらでご確認ください。

1.サンプル動画

2つのブラウザからそれぞれ「http://127.0.0.1:8124」にアクセスして、チャットを行います。お絵かきの内容をページにアクセスしている別のブラウザに反映します。

下の表示は画面が小さいので、全画面表示にするかYoutubeのサイトで直接見た方がいいかもしれません。

2.サンプルコード

Node.jsとSocket.IOを使ったサンプルコードを掲載しておきます。

色々ネットを探しましたが(探しきれてないかもしれません)、Node.js+Socket.IOでお絵かきチャットを行う適当なコードがみつからなかったので自作しました。Node.jsやSocket.IOに精通されている方にとっては当たり前のような実装かと思います。冗長な部分等ありましたらご指摘ください。

app.js(サーバ側のソース)

var app = require('express').createServer()
  , io = require('socket.io').listen(app);
app.listen(8124);
 
app.get('/', function (req, res) {
    res.sendfile(__dirname + '/index.html');
});
 
io.sockets.on('connection', function (socket) {
 
    // クライアントからメッセージ受信
    socket.on('clear send', function () {
 
        // 自分以外の全員に送る
        socket.broadcast.emit('clear user');
    });
 
    // クライアントからメッセージ受信
    socket.on('server send', function (msg) {
 
        // 自分以外の全員に送る
        socket.broadcast.emit('send user', msg);
    });
 
    // 切断
    socket.on('disconnect', function () {
        io.sockets.emit('user disconnected');
    });
});

index.html(クライアント側のソース:赤色部分がWebsocket関連の処理)

<!doctype html>
<html>
<head>
<meta charset="utf-8"> 
<title>Canvas</title>
<style>
body {
    margin: 20px;
}
canvas {
    top: 20px;
    left: 20px;
    border: 5px solid #ccc;
}
#button {
    position: relative;
    top: -55px;
    left: 345px;
}
#save {
    font-size: 120%;
    width: 80px;
    height: 50px;
}
#clear {
    font-size: 120%;
    width: 80px;
    height: 50px;
}
ul {
    margin: 0;
    padding: 0;
    list-style: none;
}
li {
    cursor:pointer;
    cursor:hand;
    display:inline-block;
    border: 1px solid #000;
    width: 50px;
    height: 50px;
}
</style>
</head>
<body>
<canvas width="500" height="400"></canvas>
<ul>
<li style="background-color:#000"></li>
<li style="background-color:#f00"></li>
<li style="background-color:#0f0"></li>
<li style="background-color:#00f"></li>
<li style="background-color:#ff0"></li>
<li style="background-color:#fff"></li>
</ul>
<div id="button">
<input type="button" id="save" value="保存" />
<input type="button" id="clear" value="消去" />
</div>
 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
$(function() {
    var offset = 5;
    var fromX;
    var fromY;
    var drawFlag = false;
    var context = $("canvas").get(0).getContext('2d');
    var socket = io.connect('http://localhost');
 
    // サーバからメッセージ受信
    socket.on('send user', function (msg) {
        context.strokeStyle = msg.color;
        context.lineWidth = 2;
        context.beginPath();
        context.moveTo(msg.fx, msg.fy);
        context.lineTo(msg.tx, msg.ty);
        context.stroke();
        context.closePath(); 
    });
 
    socket.on('clear user', function () {
        context.clearRect(0, 0, $('canvas').width(), $('canvas').height());
    });
 
    $('canvas').mousedown(function(e) {
        drawFlag = true;
        fromX = e.pageX - $(this).offset().left - offset;
        fromY = e.pageY - $(this).offset().top - offset;
        return false;  // for chrome
    });
 
    $('canvas').mousemove(function(e) {
        if (drawFlag) {
            draw(e);
        }
    });
 
    $('canvas').on('mouseup', function() {
        drawFlag = false;
    });
 
    $('canvas').on('mouseleave', function() {
        drawFlag = false;
    });
 
    $('li').click(function() {
        context.strokeStyle = $(this).css('background-color');
    });
 
    $('#clear').click(function(e) {
        socket.emit('clear send');
        e.preventDefault();
        context.clearRect(0, 0, $('canvas').width(), $('canvas').height());
    });
 
    function draw(e) {
        var toX = e.pageX - $('canvas').offset().left - offset;
        var toY = e.pageY - $('canvas').offset().top - offset;
        context.lineWidth = 2;
        context.beginPath();
        context.moveTo(fromX, fromY);
        context.lineTo(toX, toY);
        context.stroke();
        context.closePath();
 
        // サーバへメッセージ送信
        socket.emit('server send', { fx:fromX, fy:fromY, tx:toX, ty:toY, color:context.strokeStyle });
        fromX = toX;
        fromY = toY;
    }
 
    $('#save').click(function() {
        var d = $("canvas")[0].toDataURL("image/png");
        d = d.replace("image/png", "image/octet-stream");
        window.open(d,"save");
    });
});
</script>
</body>
</html>

3.サーバへのデータ送信方法

お絵かきはHTML5のcanvasを利用しています。canvasを使ったお絵かきについては「HTML5のcanvasを使ったお絵かきツール詳説」で詳しく解説しているのでそちらを参照してください。サンプルは同じものを使っています。

サーバへお絵かき情報をサーバに送信するのは、マウスで線を引いているときに起動する関数draw()で、socket.emit()を記述します(青色部分)。

クライアント側

function draw(e) {
    var toX = e.pageX - $('canvas').offset().left - offset;
    var toY = e.pageY - $('canvas').offset().top - offset;
    context.lineWidth = 2;
    context.beginPath();
    context.moveTo(fromX, fromY);
    context.lineTo(toX, toY);
    context.stroke();
    context.closePath();
 
    // サーバへメッセージ送信
    socket.emit('server send', { fx:fromX, fy:fromY, tx:toX, ty:toY, color:context.strokeStyle });
    fromX = toX;
    fromY = toY;
}

「emit」は「送信する」という意味の英単語で、サーバ・クライアントどちらでも使えます。赤色は送受信のためのカスタムイベントで、ここでは任意の文字列「server send」を記述しています。

第2パラメータには他のブラウザにお絵かきを反映させるために必要な情報をJSON形式で定義しています。fxは開始地点のX座標、fyは開始地点のY座標、txは終了地点のX座標、tyは終了地点のY座標、colorは線の色です。必要な情報があればここにどんどん追加していけばOKです。

そして、クライアントからのデータを受信できるよう、サーバ側にsocket.on()を記述します。

サーバ側

io.sockets.on('connection', function (socket) {
    socket.on('server send', function (msg) {,
        // 受信メッセージの処理
    });
});

第1パラメータのカスタムイベント名はクライアントで設定したものと一致していなければいけません。ここが一致していないとサーバで受信できません。第2パラメータはデータ受信時の関数を定義します。パラメータ(ここではmsg)には受信時のデータを定義します。

4.クライアントへの送信方法

クライアントへブロードキャストで文字列を送信するには、まず、サーバ側にio.sockets.emit()を記述します(青色部分)。

サーバ側

io.sockets.on('connection', function (socket) {
    socket.on('server send', function (msg) {
        io.sockets.emit('send user', msg);
    });
});

赤色は先程と同様、カスタムイベントで、ここでは任意の文字列「send user」を記述します。第2パラメータにはクライアントから受信したチャットデータ(msg)を設定しています。

そして、サーバからのデータを受信できるよう、クライアント側にsocket.on()を記述します。

クライアント側

// サーバからメッセージ受信
socket.on('send user', function (msg) {
    context.strokeStyle = msg.color;
    context.lineWidth = 2;
    context.beginPath();
    context.moveTo(msg.fx, msg.fy);
    context.lineTo(msg.tx, msg.ty);
    context.stroke();
    context.closePath(); 
});

受け取るクライアントでは、サーバ側のemit()の第1引数(赤色のイベント名)を必ず指定します。ここが一致していないとクライアントで受信できません。第2パラメータはデータ受信時の関数を定義します。

受信後、関数の中で座標位置や配色を変数msgから取得します。JSONデータの取得方法は、例えば開始地点のX座標であれば「msg.fx」と書きます。あとはdraw()と同じ要領でお絵かきデータを表示します。

以上です。サンプルのHTML・CSS・jQueryの説明は割愛しています。

関連記事
zenback
人気エントリー
トラックバックURL


コメントする
greeting

*必須

*必須(非表示)


ご質問のコメントの回答については、内容あるいは多忙の場合、1週間以上かかる場合があります。また、すべてのご質問にはお答えできない可能性があります。予めご了承ください。

太字イタリックアンダーラインハイパーリンク引用
[サインインしない場合はここにCAPTCHAを表示します]

コメント投稿後にScript Errorや500エラーが表示された場合は、すぐに再送信せず、ブラウザの「戻る」ボタンで一旦エントリーのページに戻り(プレビュー画面で投稿した場合は、投稿内容をマウスコピーしてからエントリーのページに戻り)、ブラウザをリロードして投稿コメントが反映されていることを確認してください。

コメント欄に(X)HTMLタグやMTタグを記述される場合、「<」は「&lt;」、「>」は「&gt;」と入力してください。例えば「<$MTBlogURL$>」は「&lt;$MTBlogURL$&gt;」となります(全て半角文字)