65.9K
CodeProject 正在变化。 阅读更多。
Home

DooScrib - 使用HTML5 Canvas进行协作

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2014 年 7 月 23 日

CPOL

8分钟阅读

viewsIcon

29012

downloadIcon

330

使用 DooScrib、Node.js、Express 和 Socket.io 通过 HTML5 Canvas 进行协作

引言

我承诺要写这个系列的文章已经有一段时间了。

在本文中,我将解释如何使用上一篇文章中讨论的 DooScrib jQuery 插件,结合 socket.ionode.jsexpress 来创建一个协作白板。

背景

本文是我计划围绕演示如何开发一个 HTML5 数字画布,让用户可以实时协作的系列文章的延续。随着每篇文章的进展,我将继续更新 DooScrib 网站,并随每篇文章发布代码,同时在我的 github 账户上保持更新的源代码。

只是一个快速提示,这里有一个普遍的假设,即您已经对 Node.js、Express 和 socket.io 有了基本的了解。如果没有,本网站有很多很棒的文章可以帮助您入门。

我将尝试涵盖和解释一些内容,但您可能需要先具备一些基本知识才能感到舒适。

设置

您当然需要确保您的计算机上已安装 Node.js。

无论您是已下载并解压了 zip 文件,还是从我的 github 存储库克隆了所有内容,仍然需要执行剩余的步骤来获取包(expressjadesocket.io)。

从命令提示符或终端窗口,您需要确保您位于 package.json 文件所在的目录。进入该目录后,您将需要执行以下命令:

npm install
这将创建 node_modules 目录,并安装 package.json 中作为依赖项列出的包,看起来像这样:
{
  "dependencies": {
    "express": "3.3.4",
    "jade": "1.1.5",
    "socket.io": "^1.0.6"
  }
}

如果愿意,您现在就可以使用以下命令运行它:

node index.js

一切安装完毕后,我们就可以开始回顾添加以实现协作的代码了。

使用代码

我将介绍客户端代码和服务器端代码。在此之前,让我先简单介绍一下一切将如何工作。

  • 客户端将数据提交到不同的事件队列到服务器(例如,移动、绘制、清除等)。
  • 服务器接收事件数据并广播到所有客户端一个类似的事件(例如,绘制、移动等)。
  • 客户端从服务器接收事件数据并根据每个事件执行相应的操作。

听起来很简单,而且实际上,当我们开始查看代码时,您会惊叹于它实际上是如何简单地整合在一起的。

服务器

设置服务器

以下代码演示了如何设置服务器。

var io = require('socket.io')(server);

传递给服务器的 server 参数是在之前的调用中创建的,因为我们正在设置 express 和 http 服务器。

var http = require('http'),
    express = require('express'),
    path = require('path');

var app = express();
var server = http.createServer(app);

服务器配置并设置好之后,下一步是添加处理新客户端连接事件的代码。以下代码演示了如何处理新连接:

io.on('connection', function (socket) {
    // add code here which handle the different events for when a client sends
    // in data up to the server.
});

处理传入消息

服务器将从客户端接收以下消息数据:

  • start - 来自客户端的事件,指示它已开始绘制一条线。
  • mousemove - 来自客户端的事件,指示鼠标已移动(未绘制)。
  • painting - 来自客户端的事件,指示鼠标已移动并且正在绘制一条线。
  • release - 来自客户端的事件,指示绘图已停止。
  • clear - 来自客户端的事件,指示用户已清除画布。

要处理来自客户端的消息,我们使用在处理连接事件时传递给我们的 socket。以下是如何处理来自客户端的消息的示例:

socket.on('message_name', function(data){
    // process data that was sent with message
});

一旦服务器收到消息,数据就可以传回给所有其他客户端。使用 socket.io,实际上有两种不同的方法可以向服务器的所有客户端广播事件。

  • io.sockets.emit - 将消息发送到服务器的所有客户端。
  • socket.broadcast.emit - 将消息发送到服务器的所有客户端,但不包括当前连接。

在这种情况下,客户端已经处理了需要做的事情。可以消除服务器需要将消息发送回它的需要,因此将使用 socket.broadcast.emit 函数。

以下代码演示了服务器如何处理来自客户端连接的各种传入消息:

io.on('connection', function (socket) {
    socket.on('start', function(data){
        console.log('starting - ', data);
        socket.broadcast.emit('starting', data);
    });	
    socket.on('mousemove', function(data){
        console.log('moving - ', data);
        socket.broadcast.emit('moving', data);
    });
    socket.on('painting', function(data){
        console.log('paint - ', data);
        socket.broadcast.emit('paint', data);
    });
    socket.on('release', function(data){
        console.log('released - ', data);
        socket.broadcast.emit('released', data);
    });
    socket.on('clear', function(data){
        console.log('clearing - ', data);
        socket.broadcast.emit('clear', data);
    });
});

当然,最后一步是设置您的服务器,以便它开始监听连接。如果您还记得,我们之前将 socket.io 服务器与 http 服务器绑定了,所以好处是所有东西都与 http 服务器绑定。以下代码是如何设置监听连接的示例:

server.listen(app.get('port'), function(){
    console.log('Express server listening on port ' + app.get('port'));
});

客户端

创建客户端 Socket

设置您的页面以使用 socket.io 比我们为配置服务器所走的步骤要简单得多。

我们之前安装了 socket.io 包,这样客户端库的传递就由我们处理了。以下演示了设置您的页面以使用 socket.io 以及创建到服务器的连接需要做什么:

包含 socket.io 库。

<script src="/socket.io/socket.io.js"></script>

创建到服务器的连接。

// change the server name to wherever you have your server deployed
var socket = io.connect('https:///');

设置 DooScrib 插件

让我们创建一个 DooScrib 插件的实例。再次提醒,在设置它时,您需要提供页面上您希望 canvas 元素创建在其中的元素。在我下面的示例中,我将一旦页面加载并准备就绪就创建所有内容。

$(document).ready(function(){
    var h = $('#surface').height();
    var w = $('#surface').width();

    surface = new $('#surface').dooScribPlugin({
        width:w,
        height:400,
        cssClass:'pad',
        penSize:4,
        onMove:function(e) {
            socket.emit('mousemove', {clientID:id, X:e.X, Y:e.Y});
        },
        onClick:function(e) {
            socket.emit('start', {clientID:id, X:e.X, Y:e.Y});
        }, 
        onPaint:function(e) {
            socket.emit('painting', {clientID:id, X:e.X, Y:e.Y, color:surface.lineColor(), pen:surface.penSize(), cap:surface.lineCap()});
        },
        onRelease:function(e) {
            socket.emit('release', {clientID:id, X:e.X, Y:e.Y});
        }
    });
});

此示例插件与之前的不同之处在于,我们现在利用了插件提供的回调事件来通知我们用户在画布上进行操作时发生的各种事件。

  • onMove - 一个表示在画布上发生移动的事件(未绘制)。
  • onClick - 一个表示由于用户在画布内进行了选择而开始绘制的事件。
  • onPaint - 一个表示在绘制时画布上发生移动的事件。
  • onRelease - 一个表示由于用户未选择画布而停止绘制的事件。

我们已经涵盖了在客户端创建 socket 的细节;这段代码现在演示了它与插件一起使用。让我快速介绍一下我们正在创建和发送的 json 对象,作为每次消息的数据。

clientIDid 是每个客户端生成的随机数,我们稍后将看到它发挥的重要作用,以便我们可以让多个客户端同时绘制。

// generate a random ID to be used as the clientID
var id = Math.round($.now()*Math.random());

接下来当然是 X 和 Y 坐标,它们作为事件数据包含在我们的回调中。最后,在 painting 消息中,我添加了来自插件的属性,这些属性描述了线条的绘制方式。

  • color - 正在绘制的线条的颜色。
  • pen - 正在绘制的线条的粗细。
  • cap - 正在绘制的线条末端的形状。

剩下的最后一步当然是让用户能够清除 canvas,以及通过消息将其传达给服务器。对于以下示例,我在页面上有一个按钮并设置了 id=clear,以便我可以接收它的点击事件。

$('#clear').click(function() {
    surface.clearSurface();
    socket.emit('clear');
});

处理服务器消息

现在剩下的唯一一部分就是处理服务器消息,以便用户可以同时看到其他人正在绘制的内容。

  • starting - 一个指示绘图已开始的消息。
  • paint - 一个指示在绘图过程中光标已移动的消息。
  • moving - 一个指示在表面上移动的消息(未绘制)。
  • released - 一个指示绘图已停止的消息。
  • clear - 一个指示清除画布请求的消息。

在回顾处理这些消息的代码之前,让我回顾一下我之前提到的关于添加 clientId 以确保我们能够区分消息来源的顾虑。在处理绘图消息时,我们需要能够存储 X 和 Y 坐标,并且不会将该信息与其他客户端混淆。

为了实现这一点,我创建了一个数组,我将使用每个消息附带的 clientId 作为索引。

var prevPoint = new Array();

在涵盖了这些信息之后,我们现在可以回顾一下完成处理服务器消息所需的所有代码:

socket.on('starting', function(data){
    console.log('starting - ', data);
    prevPoint[data.clientID] = data;
});
socket.on('moving', function(data){
    console.log('moving - ', data);
});
socket.on('paint', function(data){
    console.log('paint - ', data);
    surface.drawLine(prevPoint[data.clientID].X, prevPoint[data.clientID].Y, 
        data.X, data.Y, data.color, data.pen, data.cap);
    prevPoint[data.clientID] = data;
});
socket.on('released', function(data){
    console.log('released - ', data);
});
socket.on('clear', function(data){
    console.log('clear surface request - ', data);
    surface.clearSurface();
});

关注点

太棒了,您可能已经在本地机器上运行它了,但将其发布到互联网上会更好。我的测试和开发在 Nodejitsu 上取得了很大的成功。

下一步是什么??

在下一篇文章(或几篇文章)中,我计划将其分解成会议室,并集成以下 gist,用于将您的绘图保存和加载到 mongodb 中。

  • 这个 链接 是一个将 Base64 字符串转换为图像的示例。
  • 这个 链接 是一个将图像转换为 Base64 字符串的示例。

历史

  • 2014 年 7 月 22 日 - 初始版本
  • 2014 年 7 月 23 日 - 将所有已开发的代码迁移到 DooScrib 网站。
  • 2014 年 7 月 24 日 - 添加了下载源代码的链接。
© . All rights reserved.