原生JS实现你画我猜的一点点功能

3,556 阅读5分钟

前言

首先这问题是2019.8.6 下午一个漂亮的小姐姐问我的,当时回答的不是很好,晚上回去之后,休息了一会,然后就开始着手实现一下

然后就开始网上查查看有什么实现的思路不,结果百度上翻了几篇是没找到我想要的,我想要一个原生的实现方式,因为这样我能从中收获点东西,尽早完善前端体系,入门前端。

既然没有借鉴的,可能是暂时没找到,那就先按照我现在的思路写一个。以后真遇到了,再看看是如何实现的,对比,然后学习。


思路梳理

  • 首先通信方面不用说,你画我猜这种多人的游戏肯定是 websocket 通信 (可能还有更好的)
  • 画图技术实现也肯定确定是 canvas
    • 思考一:两个核心技术确定了,那么现在开始先做画图这一步,思考怎么通过鼠标移动(移动端是屏幕触动,我先搞下PC端的试试思路)就能在画板上展示出效果?
    • 思考二:如何记录这些信息?(因为这些信息才是需要websocket进行发送的内容)

思考一:如何在canvas上画出内容

  • 肯定要和监听事件紧密相关,想法就是当在canvas内部点下鼠标移动的时候,不断触发事件,然后根据事件中的坐标位置,渲染路径
  • 但是发现 canvas 没有监听鼠标按下之后 移动的响应事件(也可能有,但是我没找到)
  • 有一个简单的曲线救国的套路:在鼠标按下事件中绑定 鼠标移动事件,然后在鼠标抬起事件中 注销鼠标移动事件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <canvas id="canvas" width="500" height="500" style="border: 1px solid #999;"></canvas>


    <script>
        
        let canvas = document.getElementById('canvas')
        let cxt = canvas.getContext('2d')

        // 在 canvas 区域鼠标按下的时候触发
        canvas.onmousedown = (e) => {

            cxt.strokeStyle = "#3d7e9a"
            cxt.beginPath() // 开始设置路径
            cxt.moveTo(e.clientX, e.clientY) // 路径原点
            
            // 鼠标移动事件
	   canvas.onmousemove = (e) => {
                cxt.lineTo(e.clientX, e.clientY) // 路径终点
                cxt.stroke() // 输出路径轮廓(空心)
	   }
	   
        }
        
        // 在 canvas 区域鼠标抬起的时候触发
        canvas.onmouseup = () => {
            canvas.onmousemove = null
        }
        
    </script>

</body>
</html>

实现效果(Gif动图) 💝


思考二:如何记录这些信息?(不对,应该说我需要记录哪些信息)

  • 我思考我应该记录哪些信息呢 🤔,别的API可能也不会,能看到的就是我能得到移动中的每个点的信息
  • 如果把每个点的坐标都记录下来,然后发送这个,应该就可以了
  • 实践了一下,发现,如果记录每个点的信息发送几乎是没办法的,一个是数量越来越多,一个是难道每次都要清空一次画布重新绘制么?我也是傻!
  • 🔥 然后就思考到另一种方式,就是 当鼠标按下时候,到抬起这段时间内移动的点,都记录上,然后当鼠标抬起后再发送数据,这样每次发送的数据就小一些了,然后其他用户那边也不用清空画布了
  • 具体实现如下,下面有 ➕ 符号的,表示是新加的内容
<!-- 🔥 实践 -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

    <canvas id="canvas" width="400" height="400" style="border: 1px solid #999;"></canvas>
    <!-- ➕ 模拟成另一个用户 -->
    <canvas id="canvas2" width="400" height="400" style="border: 1px solid blue;"></canvas>

    <script>
        
        let canvas = document.getElementById('canvas')
        let cxt = canvas.getContext('2d')
        let dotArr = [] // ➕ 存储每次按下和抬起 中间时间移动点的位置信息

        // 在 canvas 区域鼠标按下的时候触发
        canvas.onmousedown = (e) => {

            cxt.strokeStyle = "#3d7e9a"
            cxt.beginPath() // 开始设置路径
            cxt.moveTo(e.clientX, e.clientY) // 路径原点
            
            // ➕ 先清空,然后再把数组第一个位置数据,记录为路径原点坐标
            dotArr.length = 0;
            dotArr.push({x: e.clientX,y: e.clientY})
            
            // 鼠标移动事件
	   canvas.onmousemove = (e) => {
                cxt.lineTo(e.clientX, e.clientY) // 路径终点
                cxt.stroke() // 输出路径轮廓(空心)
                
                // ➕ 记录移动中的每个点的信息
                dotArr.push({x: e.clientX,y: e.clientY})
	   }
	   
        }
        
        // 在 canvas 区域鼠标抬起的时候触发
        document.onmouseup = () => {
            canvas.onmousemove = null
            
            // ➕ 假装发送了数据,让第二个图开始绘制
            renderCanvas()
        }
        
        // ➕ 一对多发布订阅,适合用发布订阅者模式,这里就一个先这么写着
        function renderCanvas() {
            
            let canvas2 = document.getElementById('canvas2')
            let cxt2 = canvas2.getContext('2d')

            cxt2.beginPath() // 开始设置路径
            cxt2.moveTo(dotArr[0].x, dotArr[0].y) // 路径原点

            for (let i = 1, len = dotArr.length; i < len; i++) {
                cxt2.lineTo(dotArr[i].x, dotArr[i].y) // 路径重点
                cxt2.stroke() // 输出路径轮廓(空心)
            }

        }
        
    </script>

</body>
</html>

实现效果(Gif动图) 💝


思考三:websocket 部分如何实现

写作中...


思考四:如果用户画一个小时,会有什么问题,怎么解决?


思考五:还能想到其他可能的问题么?或者可能进行的优化?


思考六:我是否可以通过 TensorFlow.js 来实现一个简易的AI,识别出我画图的内容是什么?(这就跟开始的题意不符了,但是纯属处于个人兴趣,可以一试)


思考七:遇到的问题

  • 点的位置和视口的位置的问题(目测可以解决)
  • 移动时候如果鼠标移出到canvas外部,再放开鼠标,canvas 监听不了,也就没办法注销事件 (✅鼠标抬起事件绑定到document上解决此问题)

后记

都差不多实现一遍之后,我要抽离出里面能复用的模块,省的下次再写一遍了

此文章属于个人笔记文章,如果有可能千万不要推荐呀,要不就很尴尬了

我发现掘金有一篇 你画我猜的实现文章,晚上回去看