HTML5のWebsocketを使ったお絵かきチャット
HTML5のWebsocket(Node.js+Socket.IO)を使って、お絵かきチャットが行えるサンプルを作ってみました。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の説明は割愛しています。
HTML5のcanvasを使ったお絵かきツール詳説
HTML5のcanvasを使ったお絵かきツールを勉強がてら作ってみました。下記の画像リンクをクリックすればサンプルページにジャンプします。
以下、サンプルのHTMLとJavaScript(jQuery)を使って、canvasのお絵かきツールについて解説します。
探しきれてないだけと思いますが、お絵かきツールのjQueryでの実装が見つからなかったので、自力で書いてみました。いろいろ間違ってたらすいません。
1.HTML
サンプルのHTMLは次のようになっています。
<canvas width="500" height="400">お使いのブラウザはHTML5のCanvas要素に対応していません。</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>
</ul>
<div id="button">
<input type="button" id="save" value="画像で保存" />
<input type="button" id="clear" value="消去" />
</div>
1行目のcanvas要素はブラウザ上に図を描くためのものです。width属性・height属性でcanvas要素のサイズを設定します。
canvas要素はフォールバック(問題発生時の代替手段)も考慮されており、HTML5のcanvas要素に対応していないブラウザなどでは要素のコンテンツを出力します。
そのあとにあるli要素は、Canvas上で色を変更するためのものです。また、input要素でcanvasに描画された内容の消去と保存を行えるようにしています。
2.JavaScript
とりあえず全体のコードです。jQueryで書いてますがjQueryの解説は割愛しますので、分からない方は他のサイトや書籍等で調べてください。
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
var offset = 5;
var startX;
var startY;
var flag = false;
var canvas = $('canvas').get(0);
if (canvas.getContext) {
var context = canvas.getContext('2d');
}
$('canvas').mousedown(function(e) {
flag = true;
startX = e.pageX - $(this).offset().left - offset;
startY = e.pageY - $(this).offset().top - offset;
return false; // for chrome
});
$('canvas').mousemove(function(e) {
if (flag) {
var endX = e.pageX - $('canvas').offset().left - offset;
var endY = e.pageY - $('canvas').offset().top - offset;
context.lineWidth = 2;
context.beginPath();
context.moveTo(startX, startY);
context.lineTo(endX, endY);
context.stroke();
context.closePath();
startX = endX;
startY = endY;
}
});
$('canvas').on('mouseup', function() {
flag = false;
});
$('canvas').on('mouseleave', function() {
flag = false;
});
$('li').click(function() {
context.strokeStyle = $(this).css('background-color');
});
$('#clear').click(function(e) {
e.preventDefault();
context.clearRect(0, 0, $('canvas').width(), $('canvas').height());
});
$('#save').click(function() {
var d = canvas.toDataURL('image/png');
d = d.replace('image/png', 'image/octet-stream');
window.open(d, 'save');
});
});
</script>
変数定義・初期化
最初に変数の定義と初期化を行います。
var offset = 5;
var startX;
var startY;
var flag = false;
offsetは、描画するときに描画線がマウスカーソルの左上になるようにするための補正値です。startX/startYは描画の開始位置、flagは描画要否を示すフラグです。
描画コンテキストの取得
「HTML5のcanvas要素の描画コンテキストをgetElementById()の代わりにjQueryで取得する」でも紹介しましたが、以下の部分で「2D描画コンテキスト」を取得します。
var canvas = $('canvas').get(0);
if (canvas.getContext) {
var context = canvas.getContext('2d');
}
この部分はcanvasを利用するときのおまじないと思っておけばよいでしょう。
mousedownイベントでの動作
mousedownイベント(マウスボタンをクリック)で次の処理を行います。
$('canvas').mousedown(function(e) {
flag = true;
startX = e.pageX - $(this).offset().left - offset;
startY = e.pageY - $(this).offset().top - offset;
return false; // for chrome
});
flagに「true」を設定します。この値はマウスを動かしたときに利用します。startXにマウスをクリックしたカーソル位置のX座標、startYにY座標を設定します。
「pageX」は、ブラウザの左端からの位置(もっと厳格な定義があるかもしれません)、「offset().left」はブラウザの左端からcanvas要素の左端のオフセットです。例えば、「pageX」が200、「offset().left」が20であれば、X座標のカーソル位置は180になり、そこからさらに補正値のoffsetを減算します。
「return false;」は、Google chromeでcanvas上でmousedownした後カーソルが「cursor:text」の形状になってしまうので、それを回避するための設定です。
mousemoveイベントでの動作
mousemoveイベント(マウスをクリックした状態で移動)で次の処理を行います。
$('canvas').mousemove(function(e) {
if (flag) {
var endX = e.pageX - $('canvas').offset().left - offset;
var endY = e.pageY - $('canvas').offset().top - offset;
context.lineWidth = 2;
context.beginPath();
context.moveTo(startX, startY);
context.lineTo(endX, endY);
context.stroke();
startX = endX;
startY = endY;
}
});
mousedownイベントで設定したflagを判定して、trueの場合のみ処理を続行します。これは、たとえばcanvasの外側からマウスをクリックした状態でcanvasに進入してきた場合に描画動作を行わないようにするためです。
endX/endYはさきほどのstartX/startYと同じです、マウスをクリックした位置よりも僅かでもマウスが動くとこのmousemoveが発動して、endX/endYを取得します。
次に、描画コンテキストに値を設定します。lineWidthは描画線の太さ、beginPath()は現在のパスをリセットします。
moveTo()で新しいサブパスの開始点を座標指定し、lineTo()で直前の座標と指定座標を結ぶ直線を引きます。stroke()は現在の線のスタイルでサブパスを輪郭表示するためのものです。さきほど設定したlineWidthがスタイルに該当します。
描画を終えたら次のmousemoveイベントに備えて、endX/endYの値をstartX/startYに設定します。
これで大体お分かりと思いますが、お絵かきツールは短い線の描画の集合ということです。
mousemoveイベント・mouseleaveイベントでの動作
mouseupイベント(マウスのクリックをやめたとき)またはmouseleaveイベント(マウスがcanvas要素からはずれたとき)で次の処理を行います。
$('canvas').on('mouseup', function() {
flag = false;
});
$('canvas').on('mouseleave', function() {
flag = false;
});
flagに「false」を設定して、その状態でcanvas要素上でマウスを動かしても描画されないようにします。
描画色の変更
次のコードで描画色の変更をできるようにします。
$('li').click(function() {
context.strokeStyle = $(this).css('background-color');
});
li要素で用意した背景色の部分をクリックすることで、その色を描画コンテキストのstrokeStyleに設定します。strokeStyleは線や輪郭の色などを指定するためのものです。
canvasのクリア
id属性「clear」のinput要素のclickイベントを契機にcanvasのクリアを行います。
$('#clear').click(function(e) {
e.preventDefault();
context.clearRect(0, 0, $('canvas').width(), $('canvas').height());
});
preventDefaultはイベントのキャンセルを通知するメソッドです。clearRect()は、引数で指定したcanvas上の矩形のすべてのピクセルを背景色でクリアします。
canvasの描画を画像で保存
canvasの内容は画像で保存できるようです。id属性「save」のinput要素のclickイベントを契機に画像の保存を行います。
$('#save').click(function() {
var d = canvas.toDataURL('image/png');
d = d.replace('image/png', 'image/octet-stream');
window.open(d, 'save');
});
toDataURL()は、canvasのイメージに対するdata:URLを返却(=ピクセルデータをBase64形式に変換)します。replace()でMIME形式の「image/png」を「image/octet-stream」に変換して、window.open()することでファイルとして出力できます。
保存したあとは、ファイル名に拡張子「.png」を付与してください。
3.参考サイト
参考サイトは下記です。ありがとうございました。
HTML5のcanvas要素の描画コンテキストをgetElementById()の代わりにjQueryで取得する
HTML5のcanvas要素の描画コンテキストを、getElementById()の代わりにjQueryで取得する方法です。jQueryな方はタイトルだけで記事の主旨がお分かりと思いますので読み飛ばしてください(笑)。
1.canvas要素の描画コンテキストとは
前置きになりますが、canvas要素は描画機能にアクセスするためのgetContext()というDOMメソッドを持っています。canvas要素のDOMノードを取り出し、取り出したDOMノードに対してgetContext('2d')を実行することで、描画機能にアクセス可能な「描画コンテキスト」を取得します。
getContext()の引数の「2d」は、描画コンテキストの種類を示し、現在「2d」固定です。要するに「2d」を指定することで「2D描画コンテキスト」を取得します。将来的に、例えば「3d」という引数が増えて3D描画コンテキストを取得できるようになるかもしれません。
2.getElementById()が使われる箇所
ネットで色々調べたところ、描画コンテキスト取得のサンプルは概ね次のようになっていました。以下はMDNからの引用です。
<script>
var canvas = document.getElementById('tutorial');
if (canvas.getContext){ // 未サポートブラウザでの実行を抑止
var ctx = canvas.getContext('2d');
// 描画用のコードを記述
}
</script>
<canvas id="tutorial" width="150" height="150"></canvas>
ポイントは、getContext()を実行するためのDOM要素「canvas」を取得するためにgetElementById()を実行している赤色部分で、単純にこの部分をjQueryで表現できないかと思った次第です。
3.jQueryを適用する
getElementById()の代わりにjQueryを適用するには、次のように記述します。
<script>
var canvas = $('#tutorial').get(0);
if (canvas.getContext){ // 未サポートブラウザでの実行を抑止
var ctx = canvas.getContext('2d');
// 描画用のコードを記述
}
</script>
<canvas id="tutorial" width="150" height="150"></canvas>
または、
<script>
var canvas = $('#tutorial')[0];
if (canvas.getContext){ // 未サポートブラウザでの実行を抑止
var ctx = canvas.getContext('2d');
// 描画用のコードを記述
}
</script>
<canvas id="tutorial" width="150" height="150"></canvas>
このように指定する理由は、$()で返却される値が配列になっているためです。id指定で返却値が1つと分かっている場合でも配列で返却されます。
jQueryオブジェクトとして扱う場合は配列を意識することはあまりないと思いますが、ここではDOM要素として取得するために配列を意識した処理を行っています。
ちなみに、未サポートブラウザのチェックが不要であれば次のように書けます。
<script>
var context = $('#tutorial').get(0).getContext('2d');
// 描画用のコードを記述
}
</script>
<canvas id="tutorial" width="150" height="150"></canvas>
以上です。認識誤り等ありましたらご指摘ください。