【问题标题】:two people drawing on same canvas两个人在同一张画布上画画
【发布时间】:2016-06-07 15:13:59
【问题描述】:

我正在 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 方法中将颜色改回原来的颜色,但这似乎不起作用

【问题讨论】:

  • 您需要跟踪每个客户的“最后一点”,并在向客户的“最后一点”发出context.lineTo(mouse.x,mouse.y) 之前执行moveTo(这也适用于颜色,因此您可以设置正确的客户端颜色)。
  • 了解颜色,但我不太清楚点,我是否必须在每次绘制后或其他客户端触发 mouseup 事件后移动到旧点
  • 尝试在你的 mousedown 处理程序 (socketEngage) 上做(加上那里的原始代码)mouse.last_x = e.offsetX; mouse.last_y = e.offsetY;(函数的开始),在你的 mousemove 处理程序 (socketPutPoint) mouse.last_x = mouse.x; mouse.last_y = mouse.y; (函数的开始),在putPoint 之前context.lineTo(mouse.x,mouse.y) 添加if (mouse.last_x && mouse.last_y) context.moveTo(mouse.last_x,mouse.last_y); 以了解我在说什么=)希望你能做剩下的调整。
  • 感谢提示 :) 我修复了颜色一,第一行几乎固定,请写一个答案以便我接受

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


【解决方案1】:

一些白板提示:

以下所有代码都是伪代码!

  • 使用 websocket 进行通信。几个流行的 websocket 库是 SocketIOSignalR。当不支持 websocket 时,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

一个好的白板应用程序还有很多其他功能,但我现在只有这么多时间了……祝你的项目好运! :-)

【讨论】:

  • 这是我读过的关于白板的最好解释。非常感谢!! :) .
【解决方案2】:

您需要跟踪每个客户的“最后一点”,并在发出context.lineTo(mouse.x,mouse.y) 之前对客户的“最后一点”执行moveTo(这也适用于颜色,以便您可以设置正确的客户的颜色)。

要获得一个想法,您可以尝试这样做:

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

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

(函数的开头)

  • 在您的鼠标移动处理程序中 (socketPutPoint)

    mouse.last_x = 鼠标.x; mouse.last_y = mouse.y;

(函数的开头)

  • putPoint 之前context.lineTo(mouse.x,mouse.y) 添加

    如果 (mouse.last_x && mouse.last_y) context.moveTo(mouse.last_x,mouse.last_y);

希望你能做剩下的调整。

【讨论】:

  • 使用白板时,保持所有绘图“原子”非常重要 -- 每个路径绘图都应该是完整的,包括样式:.beginPath.moveTo.lineTo.strokeStyle='something'stroke不要让路径打开: 所以不要为engagedisengage 设计套接字应用程序(这会使绘图操作不完整)。这意味着您应该等待用户拖动操作完成,然后再发出完整的绘图操作。
  • 嗨@mar​​kE,如果我当时理解你的话,我不应该在用户拖动鼠标时发出事件,那么我应该如何实时向所有其他用户显示用户绘制的内容?你能解释一下吗,也许有一个答案,谢谢:)
猜你喜欢
  • 2022-09-27
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 2015-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多