17·灵魂前端工程师养成-JavaScript实现canvas画板

2022-09-26 16:45:47 浏览数 (1)

  • 利用JS做出画图板

-曾老湿, 江湖人称曾老大。


-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。


利用JS做出画图板


准备工作

在VScode中创建一个新的项目,并且初始化git仓库

新建一个 html 和一个 CSS,初始化git仓库

代码语言:javascript复制
MacBook-Pro:canvas-demo-1 driverzeng$ git init .
MacBook-Pro:canvas-demo-1 driverzeng$ git add .
MacBook-Pro:canvas-demo-1 driverzeng$ git commit -v

有人看到上面这张图的时候,可能会秒变 黑人问号脸,What The Fuck? 这张图来的很突兀,没有错,正如你所看到的,我们今天要用代码,实现一个画板。可以让人画画的画板,你没有听错,也没有看错...

CanVas教程:MDN canvas

首先使用VScode,打开网页,方便调试

代码语言:javascript复制
MacBook-Pro:canvas-demo-1 driverzeng$ http-server . -c-1
Starting up http-server, serving .
Available on:
  http://127.0.0.1:8080
  http://192.168.1.102:8080


鼠标点击

首先,如果我们要做个画板,我们需要知道,用户想要画线条之类的东西,那么他们的鼠标第一下的点击会是在哪里呢?

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="canvas"></div>
</body>
</html>
代码语言:javascript复制
*{margin: 0;padding: 0;box-sizing: border-box;}

//给canvas加个样式
#canvas{
    height: 100vh;
    border: 1px solid red;
}

通过 JS 来调试获取用户第一次点击的坐标位置

代码语言:javascript复制
    <script>
        canvas.onclick = (e)=>{
            console.log(e)
        }
    </script>

打开开发者工具,查看console,然后随便点击页面的白色位置,就会出现MouseEvent这个就是鼠标点击事件。

我们可以看见有好几个X 和 Y,到底使用哪个呢?

我们先尝试一下clientXclientY

代码语言:javascript复制
    <script>
        canvas.onclick = (e)=>{
            console.log(e.clientX)
            console.log(e.clientY)
        }
    </script>

点击了很多次,发现这个坐标可以检测到我们鼠标点击的位置,于是接下来,我们需要,在用户点击的地方就出现一个圆点。

代码语言:javascript复制
    <script>
        canvas.onclick = (e)=>{
            console.log(e.clientX)
            console.log(e.clientY)
           <!-- 让文档创建一个div的元素,这个是已经写好的api我们可以直接调用 -->
            let div= document.createElement('div')
           <!-- 接下来我们要让div出现在鼠标点击的位置,使用CSS的绝对定位 -->
            div.style.position = 'absolute'
            div.style.left = e.clientX   'px'
            div.style.top = e.clientY   'px'
            div.style.border = '1px solid red'
            div.style.width = '5px'
            div.style.height= '5px'
        }
    </script>

写完之后,我们再去点击页面,会发现.........咦?还是什么东西都没有,此时,再看一下上面的代码,我们只是创建了一个div,但是并没有说把这个div创建在哪里,所以此时这个div是在内存里,我们需要把它加入到页面当中。

代码语言:javascript复制
canvas.appendChild(div)

如果你写完了,你点击后,你会发现,这个方框会出现在鼠标的右下角,而不是出现在鼠标的正中间,所以我们还需要把这个框修改到鼠标的正中间。

代码语言:javascript复制
div.style.width = '6px'
div.style.height= '6px'
div.style.marginLeft = '-3px'
div.style.marginTop = '-3px'

接下来 ,我们需要把方框变成圆的,变成黑色实心的,去掉红色边框。

代码语言:javascript复制
    <script>
        canvas.onclick = (e)=>{
            console.log(e.clientX)
            console.log(e.clientY)
            let div= document.createElement('div')
            div.style.position = 'absolute'
            div.style.left = e.clientX   'px'
            div.style.top = e.clientY   'px'
            div.style.width = '6px'
            div.style.height= '6px'
            div.style.marginLeft = '-3px'
            div.style.marginTop = '-3px'
            div.style.borderRadius = '50%'
            div.style.backgroundColor = 'black'
            canvas.appendChild(div)
        }
    </script>

提交第一版,可以获取到鼠标的位置,并且画出点.

代码语言:javascript复制
MacBook-Pro:canvas-demo-1 driverzeng$ git add .
MacBook-Pro:canvas-demo-1 driverzeng$ git commit -v

使用canvas画线

刚才我们已经可以准确的获取到鼠标的位置,并且画出点来,现在我们需要把这些点连成线。

如图所示,我们把 onclick 更改成 onmousemove,在页面中,只要鼠标移动,就会出现黑点,慢慢移动就会变成线,但是如果移动速度很快,就会发现是断断续续的点。

所以我们需要忘记刚才的代码,有直接的方法就是 canvas ,学习官方的示例代码。

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>
        // 画线
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");

        // 控制颜色
        ctx.fillStyle = "rgb(200,0,0)";
        // 控制 横坐标 纵坐标 宽度 高度
        ctx.fillRect (10, 10, 55, 50);
    </script>
</body>
</html>
代码语言:javascript复制
*{margin: 0;padding: 0;box-sizing: border-box;}

#canvas{
    height: 100vh;
    width: 100vw;
    display: block;
    border: 1px solid red;
}

但是会发现,这个红色的框会很模糊,马赛克... AV画质。

就是因为我们在CSS中设置了宽度和高度。

canvas的宽 高 默认就设置了。

去掉CSS中的 宽高,使用用户的屏幕宽 高 来定义canvas的宽高。

但是,每个用户的宽和高又不一样,我们又不能把canvas的 宽高写死了,所以我们需要用到JS来获取用户屏幕的宽高

代码语言:javascript复制
网页可见区域宽: document.body.clientWidth  
网页可见区域高: document.body.clientHeight  
网页可见区域宽: document.body.offsetWidth (包括边线的宽)  
网页可见区域高: document.body.offsetHeight (包括边线的高)  
网页正文全文宽: document.body.scrollWidth  
网页正文全文高: document.body.scrollHeight  
网页被卷去的高: document.body.scrollTop  
网页被卷去的左: document.body.scrollLeft  
网页正文部分上: window.screenTop  
网页正文部分左: window.screenLeft  
屏幕分辨率的高: window.screen.height  
屏幕分辨率的宽: window.screen.width  
屏幕可用工作区高度: window.screen.availHeight  
屏幕可用工作区宽度: window.screen.availWidth  
代码语言:javascript复制
console.log(document.body.clientWidth)

获取一下宽度 试试

代码语言:javascript复制
    <script>

        
        // 画线
        var canvas = document.getElementById("canvas");
        
        canvas.width = document.body.clientWidth
        canvas.height = document.body.clientHeight

        var ctx = canvas.getContext("2d");

        ctx.fillStyle = "rgb(200,0,0)";
        ctx.fillRect (10, 10, 55, 50);

    </script>

会发现,这个宽度 和 高度,是根据body的宽高来定的所以,不是这个

代码语言:javascript复制
canvas.width = document.documentElement.clientWidth
canvas.height = document.documentElement.clientHeight

最终答案,使用documentElement,文档的高度

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>

        
        // 画线
        var canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight

        var ctx = canvas.getContext("2d");

        ctx.fillStyle = "rgb(200,0,0)";
        ctx.fillRect (10, 10, 55, 50);

    </script>
</body>
</html>
代码语言:javascript复制
*{margin: 0;padding: 0;box-sizing: border-box;}

#canvas{
    display: block;
}

现在使用canvas,开始用鼠标来填充图案,先获取鼠标点击事件看看。

代码语言:javascript复制
    <script>    
        // 画线
        var canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight

        var ctx = canvas.getContext("2d");

        ctx.fillStyle = "black";
        ctx.fillRect (10, 10, 55, 50);
        canvas.onclick = (e) =>{
            console.log(e.clientX)
            console.log(e.clientY)
        }
    </script>

依然可以获取到,因此,我们可以按照刚才的方式,先画点

然后居中,连线

但是现在鼠标,停不下来,只要移动就会画线。

所以我们可以设置一个信号,比如开车的时候,当你看到红灯,就会停车,当你看到绿灯就会开车。

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>    
        // 画线
        let canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight

        let ctx = canvas.getContext("2d");
        let painting = false

        ctx.fillStyle = "black";

        canvas.onmousedown = () => {
            painting = true
        }
        
        canvas.onmousemove = (e) =>{
            if (painting === true) {
                ctx.fillRect (e.clientX -3, e.clientY -3, 6, 6);
            }  else {
                console.log('啥都不做')
            }
        }

        canvas.onmouseup = () => {
            painting = false
        }
    </script>
</body>
</html>

现在把线条的方框,改成圆形

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>    
        // 画线
        let canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight

        let ctx = canvas.getContext("2d");
        let painting = false

        ctx.fillStyle = "black";
        ctx.strokeStyle = 'none'

        canvas.onmousedown = () => {
            painting = true
        }
        
        canvas.onmousemove = (e) =>{
            if (painting === true) {
                ctx.beginPath();
                ctx.arc(e.clientX,e.clientY,5,0,2 * Math.PI);
                ctx.stroke();
                ctx.fill();
            }  else {
                console.log('啥都不做')
            }
        }

        canvas.onmouseup = () => {
            painting = false
        }
    </script>
</body>
</html>

提交git仓库,使用canvas画线

代码语言:javascript复制
MacBook-Pro:canvas-demo-1 driverzeng$ git add .
MacBook-Pro:canvas-demo-1 driverzeng$ git commit -v

优化:手机使用

首先先要检测,是否支持触屏,鼠标点击事件,在手机上肯定是不好使了。

于是乎,经过多次辗转反侧的找文档,发现:

代码语言:javascript复制
let isTouchDevice = 'ontouchstart' in document.documentElement;
console.log(isTouchDevice)

PC端,false

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>    
        // 画线
        let canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight
        let ctx = canvas.getContext("2d");
        let painting = false
        ctx.fillStyle = "black";
        ctx.strokeStyle = 'none'

        let isTouchDevice = 'ontouchstart' in document.documentElement;
        console.log(isTouchDevice)


        if(isTouchDevice){
            canvas.ontouchmove = (e) => {
                let x = e.touches[0].clientX
                let y = e.touches[0].clientY
                ctx.beginPath();
                ctx.arc(x,y,5,0,2 * Math.PI);
                ctx.stroke();
                ctx.fill();
            }

        }else{
            canvas.onmousedown = () => {
                painting = true
            }
        
            canvas.onmousemove = (e) =>{
                if (painting === true) {
                    ctx.beginPath();
                    ctx.arc(e.clientX,e.clientY,5,0,2 * Math.PI);
                    ctx.stroke();
                    ctx.fill();
                }
             }

            canvas.onmouseup = () => {
                painting = false
            }     
        }


    </script>
</body>
</html>

优化,在点和点之间,连成一条线。

我们先要知道,如何画一条线

代码语言:javascript复制
let ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.strokeStyle = 'none'
ctx.beginPath();
ctx.moveTo(125,125);
ctx.lineTo(125,45);
ctx.lineTo(45,125);
ctx.closePath();
ctx.stroke();

这个三角形是怎么做到的呢?

我们可以通过修改代码来看

beginPath: 是开始 moveTo:起点 lineTo:从起点画一根线 stroke:描边 fill:填充颜色

如果不要后面的,只要前面的起点和第一条线。

这样就出来了一条线,这样 我们可以写个画线的函数

通过传参 的方式

代码语言:javascript复制
        function drawLine(x1,y1,x2,y2){
            ctx.beginPath();
            ctx.moveTo(x1,y1);
            ctx.lineTo(x2,y2);
            ctx.stroke();
        }

        drawLine(0,0,500,500)

使用drawLine函数

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>    
        // 画线
        let canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight
        let ctx = canvas.getContext("2d");
        let painting = false
        ctx.fillStyle = "black";
        ctx.strokeStyle = 'none'

        let isTouchDevice = 'ontouchstart' in document.documentElement;

        function drawLine(x1,y1,x2,y2){
            ctx.beginPath();
            ctx.moveTo(x1,y1);
            ctx.lineTo(x2,y2);
            ctx.stroke();
        }


        if(isTouchDevice){
            canvas.ontouchmove = (e) => {
                console.log(e.touches[0].clientX)

                let x = e.touches[0].clientX
                let y = e.touches[0].clientY
                ctx.beginPath();
                ctx.arc(x,y,5,0,2 * Math.PI);
                ctx.stroke();
                ctx.fill();
            }

        }else{
            canvas.onmousedown = () => {
                painting = true
            }
        
            canvas.onmousemove = (e) =>{
                if (painting === true) {
                    drawLine(0,0,e.clientX,e.clientY)
                }
             }

            canvas.onmouseup = () => {
                painting = false
            }     
        }
    </script>
</body>
</html>

每画一个点,都会从(0,0)这个坐标点,开始,所以变成这样了。

所以,我们需要修改一下起点,改成上一个点

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>    
        // 画线
        let canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight
        let ctx = canvas.getContext("2d");
        let painting = false
        ctx.fillStyle = "black";
        ctx.strokeStyle = 'none'

        let isTouchDevice = 'ontouchstart' in document.documentElement;
        let last

        function drawLine(x1,y1,x2,y2){
            ctx.beginPath();
            ctx.moveTo(x1,y1);
            ctx.lineTo(x2,y2);
            ctx.stroke();
        }


        if(isTouchDevice){
            canvas.ontouchmove = (e) => {
                console.log(e.touches[0].clientX)

                let x = e.touches[0].clientX
                let y = e.touches[0].clientY
                ctx.beginPath();
                ctx.arc(x,y,5,0,2 * Math.PI);
                ctx.stroke();
                ctx.fill();
            }

        }else{
            canvas.onmousedown = () => {
                painting = true
                last  = [e.clientX, e.clientY]
            }
        
            canvas.onmousemove = (e) =>{
                if (painting === true) {
                    drawLine(last[0],last[1],e.clientX,e.clientY)
                }
             }

            canvas.onmouseup = () => {
                painting = false
            }     
        }
    </script>
</body>
</html>

现在又变成每次从第一个点,开始画线,我们需要让上一次实时更新

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>    
        // 画线
        let canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight
        let ctx = canvas.getContext("2d");
        let painting = false
        ctx.fillStyle = "black";
        ctx.strokeStyle = 'none'

        let isTouchDevice = 'ontouchstart' in document.documentElement;
        let last

        function drawLine(x1,y1,x2,y2){
            ctx.beginPath();
            ctx.moveTo(x1,y1);
            ctx.lineTo(x2,y2);
            ctx.stroke();
        }


        if(isTouchDevice){
            canvas.ontouchmove = (e) => {
                console.log(e.touches[0].clientX)

                let x = e.touches[0].clientX
                let y = e.touches[0].clientY
                ctx.beginPath();
                ctx.arc(x,y,5,0,2 * Math.PI);
                ctx.stroke();
                ctx.fill();
            }

        }else{
            canvas.onmousedown = (e) => {
                painting = true
                last  = [e.clientX, e.clientY]
            }
        
            canvas.onmousemove = (e) =>{
                if (painting === true) {
                    drawLine(last[0],last[1],e.clientX,e.clientY)
                    last = [e.clientX,e.clientY]
                }
             }

            canvas.onmouseup = () => {
                painting = false
            }     
        }
    </script>
</body>
</html>

加上线的宽度,太细了...

变粗了之后,会发现,画线中间会有缝隙。所以我们还需要调整一个参数

手机适配:

代码语言:javascript复制
<!DOCTYPE html>
<html lang="zh-CN">
<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>画板</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <canvas id='canvas'></canvas>
    <script>    
        // 画线
        let canvas = document.getElementById("canvas");

        canvas.width = document.documentElement.clientWidth
        canvas.height = document.documentElement.clientHeight
        let ctx = canvas.getContext("2d");
        let painting = false

        ctx.fillStyle = "black";
        ctx.strokeStyle = 'none'
        ctx.lineWidth = 20;
        ctx.lineCap = 'round'

        let isTouchDevice = 'ontouchstart' in document.documentElement;
        let last

        function drawLine(x1,y1,x2,y2){
            ctx.beginPath();
            ctx.moveTo(x1,y1);
            ctx.lineTo(x2,y2);
            ctx.stroke();
        }


        if(isTouchDevice){
            canvas.ontouchstart = (e) => {
                let x = e.touches[0].clientX
                let y = e.touches[0].clientY
                last = [x,y]
            }

            canvas.ontouchmove = (e) => {
                let x = e.touches[0].clientX
                let y = e.touches[0].clientY
                drawLine(last[0],last[1],x,y)
                last = [x,y]
            }

        } else {
            canvas.onmousedown = (e) => {
                painting = true
                last  = [e.clientX, e.clientY]
            }
        
            canvas.onmousemove = (e) =>{
                if (painting === true) {
                    drawLine(last[0],last[1],e.clientX,e.clientY)
                    last = [e.clientX,e.clientY]
                }
             }

            canvas.onmouseup = () => {
                painting = false
            }     
        }
    </script>
</body>
</html>

0 人点赞