两个人在同一幅画上 canvas
two people drawing on same canvas
我正在 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 方法中将颜色改回原来的颜色,但似乎行不通
您需要跟踪每个客户的 "last point",并在发布 context.lineTo(mouse.x,mouse.y)
之前对客户的 "last point" 执行 moveTo
(这也适用于颜色,以便您可以设置正确的客户端颜色)。
要获得想法,您可以尝试这样做:
在您的 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);
希望您能完成其余的调整。
一些白板提示:
以下代码均为伪代码!
使用 websockets 进行通信。几个流行的 websocket 库是 SocketIO and SignalR。当不支持 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);
保留所有图纸非常重要(关键!)"atomic" -- 每条路径图都应完整,包括样式。 不要 启动 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.
一个好的白板应用程序还有很多,但我现在只有这些了……祝你的项目好运! :-)
我正在 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 方法中将颜色改回原来的颜色,但似乎行不通
您需要跟踪每个客户的 "last point",并在发布 context.lineTo(mouse.x,mouse.y)
之前对客户的 "last point" 执行 moveTo
(这也适用于颜色,以便您可以设置正确的客户端颜色)。
要获得想法,您可以尝试这样做:
在您的 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);
希望您能完成其余的调整。
一些白板提示:
以下代码均为伪代码!
使用 websockets 进行通信。几个流行的 websocket 库是 SocketIO and SignalR。当不支持 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);
保留所有图纸非常重要(关键!)"atomic" -- 每条路径图都应完整,包括样式。 不要 启动
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.
一个好的白板应用程序还有很多,但我现在只有这些了……祝你的项目好运! :-)