两个人画在同一个画布上

时间:2016-06-07 15:13:59

标签: javascript html5 canvas socket.io real-time

我正在html5画布中制作一个实时绘画应用程序。当一个用户在画布上绘制时,一切都很顺利,但是当两个用户同时绘制时,一切都搞砸了,例如,如果一个人改变颜色,所有客户端的颜色都会改变,并且线条从一个点开始绘制到另一个点。怎么解决这个问题?谢谢,这是我的代码。

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
canvas.width="600";
canvas.height="500";
var radius = 10;
var mouse = {x:0,y:0};
var drag = false;
var imageObj = new Image();
  imageObj.onload = function() {
    context.drawImage(imageObj, 20, 20);
 };
  imageObj.src = 'rhino4.png';
$scope.colorChange = function(color){
  Socket.emit("colorChange",color);
};
Socket.on("colorChange",function (color) {
  context.strokeStyle = color;
  context.fillStyle = color;
})
$scope.radiusChange = function(size) {
  Socket.emit("radiusChange",size);
}
Socket.on("radiusChange",function (size) {
  radius = size;
  context.lineWidth = radius*2;
})
context.lineWidth = radius*2;
var putPoint = function (mouse) {
  if(drag){
    context.lineTo(mouse.x,mouse.y)
    context.stroke();
    context.beginPath();
    context.arc(mouse.x,mouse.y,radius,0,Math.PI*2);
    context.fill();
    context.beginPath();
    context.moveTo(mouse.x,mouse.y);
    context.globalCompositeOperation='source-atop';
    context.drawImage(imageObj, 20, 20);
    context.globalCompositeOperation='source-over';
  }
}
Socket.on("putPoint",function (mouse) {
  putPoint(mouse);
});
var engage = function(mouse){
  console.log("in engage",mouse);
  drag = true;
  putPoint(mouse);
}
var disengage = function(){
  drag = false;
  context.beginPath();
}
var socketPutPoint = function(e){
  mouse.x = e.offsetX;
  mouse.y = e.offsetY;
  Socket.emit("putPoint",mouse);
}
Socket.on("engage",function (mouse) {
  console.log("engaging");
  engage(mouse);
});
var socketEngage = function (e) {
  mouse.x = e.offsetX;
  mouse.y = e.offsetY;
  console.log(mouse);
  Socket.emit("engage",mouse);
}
var socketDisengage = function (e) {
  mouse.x = e.offsetX;
  mouse.y = e.offsetY;
  console.log(mouse);
  Socket.emit("disengage",mouse);
}
Socket.on("disengage",function (mouse) {
  disengage();
})
canvas.addEventListener('mouseup',socketDisengage);
canvas.addEventListener('mouseleave',socketDisengage);
canvas.addEventListener('mousedown',socketEngage);
canvas.addEventListener('mousemove',socketPutPoint);

我想在putpoint之后将colorChange方法中的颜色改回原来的颜色,但这似乎不起作用

2 个答案:

答案 0 :(得分:2)

一些白板提示:

以下所有代码均为伪代码!

  • 使用websockets进行通信。几个流行的websocket库是SocketIOSignalR。当不支持websockets时,Websocket库通常具有回退方法。

  • 使用JSON序列化绘图数据。关于JSON的好处是它自动获取JavaScript对象/数组并从中创建一个适合websocket传输的字符串。反之亦然:自动接收JSON字符串并将字符串重新水化为JavaScript对象/数组。

    var command = {
        client:'sam', 
        points:[{x:5,y:10},...],
        // optionally add styling (strokeStyle, linewidth, etc)
    };
    
    // serialize a command 
    var jsonCommand = JSON.stringify(command);
    
    // deserialize a command
    var command = JSON.parse(jsonCommand);
    
  • 保持所有图纸“原子性”非常重要(关键!) - 每个路径绘图应该是完整的,包括样式。 启动context.beginPath并随着时间的推移发出一系列context.lineTo

    draw(command.points);
    
    // ALWAYS issue complete drawing commands
    // including styling (if any)
    function draw(points);
        var ptsLength=points.length;
        context.beginPath;
        context.moveTo(points[0].x,points[0].y);
        for(var i=0;i<ptsLength;i++){
            var pt=points[i];
            context.lineTo(pt.x,pt.y);
        }
        context.stroke();
    }
    
  • 不要让路径保持打开状态:所以不要设计套接字应用程序来发送部分绘图点(这会使绘图操作不完整)。这意味着您应该在发出完整的绘图操作之前等待用户拖动操作完成。

    var isDown=false;
    var commands=[];
    var points;
    var lastX,lastY;
    
    
    // on mousedown ...
    // reinitialize the accumulated points array
    // with the mousedown point
    function handleMouseDown(e){
    
        // tell the browser we're handling this event
        e.preventDefault();
        e.stopPropagation();
    
        // get mouse position
        lastX=parseInt(e.clientX-offsetX);
        lastY=parseInt(e.clientY-offsetY);
    
        // reset the accumulated points array
        // add the point to the accumulated points array
        points=[ {x:lastX, y:lastY} ];          
    
        // set the isDown flag
        isDown=true;
    }
    
    
    // on mousemove ...
    // add the current mouse position to the accumulated points array
    function handleMouseMove(e){
    
        if(!isDown){return;}
    
        // tell the browser we're handling this event
        e.preventDefault();
        e.stopPropagation();
    
        // get mouse position
        mouseX=parseInt(e.clientX-offsetX);
        mouseY=parseInt(e.clientY-offsetY);
    
        // draw the newest local path segment
        // so the local user can see while they're drawing
        context.beginPath();
        context.moveTo(lastX,lastY);
        context.lineTo(mouseX,mouseY);
        context.stroke();
        // save the last x,y
        lastX=mouseX;
        lastY=mouseY;
    
        // add the point to the accumulated points array
        points=[ {x:mouseX, y:mouseY} ];
    }
    
    
    // on mouseup ...
    // end the current draw operation
    // and add the points array to the commands array
    function handleMouseOut(e){
    
        // tell the browser we're handling this event
        e.preventDefault();
        e.stopPropagation();
    
        // clear the isDown flag
        isDown=false;
    
        // add the current set of points 
        // to the accumulated commands array
        commands.push({
            client:myName,
            stroke:myCurrentStrokeColor,
            points:points
        });
    
    }
    
  • 使用单独的循环向服务器发出本地绘图命令并绘制传入的远程绘图命令:

    // vars to schedule drawing from remote clients
    // and sending local drawings to server
    var nextDrawingTime, nextSendingTime;
    var drawingTimeDelay=1000; // or some other delay, but don't be a burden!
    var sendingTimeDelay=1000; // or some other delay, but don't be a burden!
    
    // start the processing loop (it runs continuously non-stop)
    requestAnimationFrame(process);
    
    function process(time){
    
        // a simplification ...
        // don't interrupt if the local user is drawing
        if(isDown){ return; }
    
        // draw incoming strokes
        if(time>nextDrawingTime && receivedCommands.length>0){
    
            // set the next drawing time for remote draws
            nextDrawingTime=time+drawingTimeDelay;
    
            // draw all accumulated received commands
            for(var i=0;i<receivedCommands.length;i++){
                var c=receivedCommands[i];
                if(c.client!==myName){
                    draw(c.points);
                }
            }
            receivedCommands.length=0;
    
        // emit outgoing strokes
        } else if(time>nextSendingTime && commands.length>0){
    
            // set the next emitting time for locally drawing paths
            nextSendingTime=time+sendingTimeDelay;
    
            // JSON.stringify
            var jsonPacket=JSON.stringify(commands);
    
            // reset the set of local drawing commands
            commands=[];
    
            // emit to server for broadcast to everyone
    
        }
    
        requestAnimationFrame(process);
    }
    
  • 让服务器执行一些重要任务:

    • 如果您选择的websockets库不自动包含时间戳,请为每个广播添加时间戳。

    • 保存所有收到的绘图命令(数据库),因为出现问题,您可能需要不时完全重新同步客户端。

  • Mousemove每秒发射约30次,因此会积累大量的点数。要减少数据传输大小,请考虑使用路径缩减算法来删除冗余点。一个好的算法是Douglas Peucker path simplification algorithm

对于一款优秀的白板应用程序来说还有很多东西,但这是我现在所有的时间......祝你的项目好运! : - )

答案 1 :(得分:1)

您需要跟踪每个客户的最后一点&#34;在向context.lineTo(mouse.x,mouse.y)发出moveTo之前,请向客户提供最后一点&#34; (这也适用于颜色,因此您可以设置正确的客户端颜色)。

要获得一个ideia,你可以尝试:

    在你的mousedown处理程序(socketEngage)上执行
  • (加上那里的原始代码)

    mouse.last_x = e.offsetX; mouse.last_y = e.offsetY;

(功能开始)

    您的mousemove处理程序(socketPutPoint

    中的
  • mouse.last_x = mouse.x; mouse.last_y = mouse.y;

(功能开始)

    {li>

    并在putPoint添加

    之前context.lineTo(mouse.x,mouse.y)

    if(mouse.last_x&amp;&amp; mouse.last_y)     context.moveTo(mouse.last_x,mouse.last_y);

希望你能做其余的调整。