多人协作共享画板——多人画板的bug解决

上一篇博客,我们分析了多人画板bug的产生原因,并且探索了一个解决的方法。现在让我们来实现它吧!

实现思路

场景再现:
我们再来回顾一下,用户绘制一条路径或者线条的步骤,一个moveTo方法,接着是一系列lineTo方法,并且注意在这个过程中画笔是一直保持在画布上的。你可以想象一下,你手握一只笔,在画布上从左往右画一条直线,就是那么简单的过程。那么现在,让我们加大一点复杂程度,此刻又来了一个人,也握住了笔。他想要画一条和你画的线平行的路径。现在,你们同时开始绘制路径了,你在上方画,他在下方画。对于画笔来说,就是上方画一笔,下方画一笔,笔在上下两条平行线之间来回切换,而且笔是一直在画布上的,我想大家都可以想象到那种混论的场景了。

解决方案:
我们给上面的两个人起一个代号:A、B。用户A先开始画,他向前移动了一点,然后他记住了他现在的位置。然后用户B开始画,他向前移动了一点,他也记住了他的位置。此刻又轮到了用户A,他先跳到他记住的那一点,然后开始绘制一点,更新当前点为他记住的点。。。这样每个人都记着自己上一步到达了哪里。但他再次开始绘制时,他先跳到他上次的地方,然后再开始绘制。

图例讲解
用户A、B绘制1、2、3这三个点的一种情况。
下面的1、2、3、4是步骤,假设A先画第1步,然后B画了第2步,接下来全部都是lineTo方法了,画笔只能在画布上移动,不能离开。然后A画到第3点,当时当前笔在B的第2点,所以是直接从下方拉到上方了。然后B画到他的第3点,笔是直接从上方拉到了下方。最终两条平行线绘制失败了。
在这里插入图片描述

用户A、B同时绘制,假设A先画第1步,然后B画第2步。然后A画下一步,此时需要先跳到他上一次绘制的点,然后开始画第4步(第3步用来跳点了),然后B想要绘制下一步,则需要先执行第5步,跳到上一次的位置,开始画他的第6步。
注意:B的第一步不会冲突,是因为第一步通常是按下鼠标,执行的是moveTo方法。
在这里插入图片描述

代码实现

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style type="text/css">
			* {
				margin: 0;
				padding: 0;
			}
			.rg{
				float: left;
				width: 400px;
				height: 100px;
				text-align: center;
				border: 1px black solid;
				margin-left:-1px ;
			}
			#cas{
				width: 800px;
				height: 600px;
				border: #000000 1px solid;
			}
	
			p{
				margin: 5px 0 5px 0;
			}
		</style>
	</head>
	<body>
		<div id="seclect">
			<div class="rg" id="secc">
				<p>选择画笔颜色</p>
				<input type="color" id="cl"/>
			</div>
			<div class="rg" id="secw">
				<p>选择画笔大小:&nbsp;<span id="size">1px</span></p>
				<input type="range" onchange="setLineWidth(this)" value="1" min="1" max="10"/>
			</div>
		</div>
		<div id="cas">
			<canvas id="cs" width="800" height="600"></canvas>
		</div>
		
		<script type="text/javascript">				
			var canvas = document.getElementById("cs");//获取画布
			var context = canvas.getContext("2d");
			
			const map = new Map();  // 用来记住每个人上一步的数据结构map
			
			function setLineWidth(e) {    // this 指向是就是该元素本身
			    console.log("你点击了画笔:", e);
				console.log(e.value)
				context.lineWidth = e.value;
				document.getElementById("size").innerHTML = e.value + " px";
			}
			
			/* 用户绘制的动作,可以分解为如下操作:
				1.按下鼠标
				2.移动鼠标
				3.松开鼠标
				
			   它们分别对应于鼠标的onmousedown、onmousemove和onmouseup事件。
			   并且上述操作必然是有想后顺序的,因为人的操作必然是几个操作
			   集合中的一种。所以我们需要来限定以下,过滤用户的无效操作,
			   只对按照上诉顺序的操作进行响应。
			*/
			let isDowned = false;  // 是否按下鼠标,默认是false,如果为false,则不响应任何事件。
			
			// 开始添加鼠标事件
			canvas.onmousedown = function(e) {
				let x = e.clientX - canvas.offsetLeft;
				let y = e.clientY - canvas.offsetTop;
				isDowned = true;   // 设置isDowned为true,可以响应鼠标移动事件
				console.log("当前鼠标点击的坐标为:(", x + ", " + y + ")");
				context.strokeStyle = document.getElementById("cl").value;   // 设置颜色,大小已经设置完毕了
				context.beginPath();    // 开始一个新的路径
				context.moveTo(x, y);   // 移动画笔到鼠标的点击位置
				
				// 多人协作的逻辑
				let pos = {id: 0, type: 0, x: x, y: y, color: context.strokeStyle, size: context.lineWidth}
				map.set(pos.id, pos)    // 保存当前点为上一个点
				client.send(JSON.stringify(pos))
			}
			
			canvas.onmousemove = function(e) {
				if (!isDowned) {
					return ;
				}
				let x = e.clientX - canvas.offsetLeft;
				let y = e.clientY - canvas.offsetTop;
				console.log("当前鼠标的坐标为:(", x + ", " + y + ")");
				
				// 多人协作逻辑				
				let pos = {id: 0, type: 1, x: x, y: y, color: context.strokeStyle, size: context.lineWidth}
				
				let prePoint = map.get(pos.id);
				console.log(prePoint)
				context.moveTo(prePoint.x, prePoint.y);
				context.lineTo(x, y);    // 移动画笔绘制线条
				context.stroke();
				
				map.set(pos.id, pos)    // 保存当前点为上一个点
				client.send(JSON.stringify(pos))
			}
			
			canvas.onmouseup = function(e) {
				isDowned = false;
			}
			
			/*
				在按下鼠标移动的过程中,如果移出了画布,则无法触发鼠标松开事件,即onmouseup。
				所以需要在鼠标移出画布时,设置isDowned为false。
			*/
			canvas.onmouseout = function(e) {
				isDowned = false;
			}
			
		</script>
		
		<script>
		function link () {
			client = new WebSocket("ws://crazydragon.top:10000/ws/wedraw");    //连接服务器
			
			client.onopen = function(e){
				alert('连接了');
			};
			
			client.onmessage = function (e) {
			    let data = e.data
				let pos = JSON.parse(data)
				
				console.log("接受到的消息:" + data)
				
				context.strokeStyle = pos.color   // 设置颜色
				context.lineWidth = pos.size      // 设置线宽
				if (pos.type === 0) {             // 如果该点是移动画笔,则移动画笔
				    context.beginPath()           // 开始一个新的路径
					context.moveTo(pos.x, pos.y)
					context.stroke();             // 绘制点,这行代码似乎没有作用
			
					map.set(pos.id, pos)    // 记住当前点的位置	
				} else if (pos.type === 1) {      // 如果该点是画线,就画线
				    // 对于画线,不再是直接画线,而是要区分它是谁的那一笔
					let mpos = map.get(pos.id)
					
					// 开始一个新路径
					context.beginPath();
					context.lineWidth = mpos.size;
					context.strokeStyle = mpos.color;
					
					// 然后移动到它上一点的位置
					context.moveTo(mpos.x, mpos.y)
					console.log("正在画线")
					context.lineTo(pos.x, pos.y)
					// 记录当前点的位置,覆盖原来的记录,这里默认所有的点都是moveTo了,不需要修改type了
					map.set(pos.id, pos)
					context.stroke();             // 绘制点
				} else {
					console.log("不存在的情况,直接返回")
					return
				}	
			}
			
			client.onclose = function(e){
				alert("已经与服务器断开连接\r\n当前连接状态:" + this.readyState);
			};
			
			client.onerror = function(e){
				alert("WebSocket异常!");
			};
		}
		
		function sendMsg(position){
			client.send(position);
		}
		link ()  // 直接建立websocket连接
		</script>
	</body>
	
</html>

使用一个map结构来存储每个用户绘制的当前点,作为下一次绘制的上一点。每次绘制时,则向跳到他的上一点,再进行绘制。

测试结果

在这里插入图片描述

总结

可以发现,现在两个用户(或者多个用户)是可以进行同时绘制了。 但是绘制的线条的演示和大小会进行干扰,同时这种方式绘制出来的线条或者路径,感觉丢失了一些细节,虽然整体的结构还是保持的。这是因为原来那种绘制方法被打破了:
原来的绘制方式是,一个moveTo,然后接着执行一系列的lineTo方法。
当时为了配合多个用户进行绘制,现在的绘制方式是,一个moveTo,一个lineTo,再接着moveTo,然后lineTo…,也就是说原来是直接绘制一条线段,当时现在变成了一条一条线段的绘制了。当时这个问题,在后面会有解决方式,我们放在下一篇进行讲解。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值