javascript - 两个人在同一张 Canvas 上作画

标签 javascript html canvas socket.io real-time

我正在 html5 canvas 中制作一个实时绘画应用程序。当单个用户在 Canvas 上绘图时,一切都会很好,但是当两个用户同时绘图时,一切都会变得困惑,例如,如果其中一个用户更改了颜色,则所有客户端的颜色都会更改,并且线条开始从一个点绘制到另一个点。 。如何解决这个问题?谢谢,这是我的代码。

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 方法中的颜色更改回原始颜色,但这似乎不起作用

最佳答案

一些白板提示:

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

  • 使用 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 库不会自动包含时间戳,请为每个广播添加时间戳。

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

  • 鼠标移动每秒发射约 30 次,因此会积累大量积分。为了减少数据传输大小,可以考虑使用路径缩减算法来删除冗余点。一种很好的算法是 Douglas Peucker path simplification algorithm .

一个好的白板应用程序还有很多东西,但这就是我现在所拥有的时间......祝你的项目好运! :-)

关于javascript - 两个人在同一张 Canvas 上作画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37683190/

相关文章:

javascript - Jquery:动态附加到 context.childNodes[4] 不起作用

JavaScript:将日/周转换为年

javascript - 如何检测 socket.io 上的断开连接?

html - 作为 parent 的高度和导航栏内的位置

javascript - HTML 表单提交按钮未运行与其关联的 OnSubmit 脚本

javascript - 如何获取在 HTML5 Canvas 上单击的坐标?

javascript - 如果在 Chrome 上运行,createObjectURL 不起作用

javascript - s.replace 不是sequelize.fn 'AVG' 上的函数

javascript - 获取模板并将其附加到 DOM 后的 UI-Router Hook

javascript - 如何使用响应式设计将此菜单置于移动布局的中心