准备工作

能画丁老头的画板

Canvas教程MDN

创建项目目录,并初始化git

1
2
#预览
hs . -c-1

面向谷歌编程

google搜js create div mdn

  • document.createElement('div')

google搜js insert div into page mdn

  • 在页面中添加这个元素

    1
    
    <canvas id='canvas' width="100" height="100"></canvas>
    1
    2
    3
    
    let div = document.createElement('div')
    let canvas = document.getElementById("canvas");
    canvas.appendChild(div)
    

google搜js get body mdn


代码

让canvas占满屏幕,并且没有滚动条

增加样式

div.style.xxx

点出现在鼠标中间,而不是右下角,对称,偶数

由方变圆,变实心

点击事件改成鼠标移动事件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
canvas.onclick = (e) => {

    //console.log()调试大法
    console.log(e.clientX, e.clientY)

    //创建div 在内存里,并不在页面(文档里)
    let div = document.createElement('div')
    div.id = 'canvas'

    //绝对定位
    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 = '6px'
    div.style.height = '6px'

    //点出现在鼠标中间,而不是右下角
    div.style.marginLeft = '-3px'
    div.style.marginTop = '-3px'

    //由方变圆
    div.style.borderRadius = '50%'
    div.style.backgroundColor = 'black'

    //添加已在内存中创建的节点
    canvas.appendChild(div)
}

这样会卡 JS操作DOM 低效 跨线程通信

Canvas画点

<canvas></canvas>默认的display:inline,类似于<img>

改为display:block

干掉影响总宽高的滚动条加上width: 100vw; height: 100vh;

但会导致canvas模糊,所以不行

  • <canvas></canvas>的宽高需在一开始就确定

可以在<canvas></canvas>上写宽高,但会被CSS覆盖,同<img>

  • 用JS获取屏幕宽高,并设置
1
2
canvas.width = document.body.clientWidth
canvas.height = document.body.clientHeight

但body 默认是由子元素决定,未撑开

所以要直接获取文档的高度

1
2
3
var canvas = document.getElementById("canvas");
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;

这就让canvas占满屏幕,并且没有滚动条


画点

1
2
3
4
5
6
7
8
9
//样式
var ctx = canvas.getContext("2d")
ctx.fillStyle = "orange";

canvas.onclick = (e) => {
    console.log(e.clientX)
    console.log(e.clientY)
    ctx.fillRect(e.clientX-5, e.clientY-5, 10, 10)
}

e.clientX-5让宽度减一半,使其在鼠标中心

onclick改成onmousemove

没有新建任何节点,只是在画像素点,不需要操作DOM

加开关,标记,按下,抬起鼠标左键的开关记号

let painting = false

加动作,鼠标按下

1
2
3
canvas.onmousedown = () => {
    painting = true
}

加判断,开关开启,动作执行

1
2
3
4
5
6
7
8
9
canvas.onmousemove = (e) => {
    if (painting === true) {
        console.log(e.clientX)
        console.log(e.clientY)
        ctx.fillRect(e.clientX - 5, e.clientY - 5, 10, 10)
    } else {
        console.log('什么都不做')
    }
}

加关闭触发动作

1
2
3
canvas.onmouseup = () => {
    painting = false
}

锯齿太严重,换圆形试试

1
2
3
4
ctx.beginPath();
ctx.arc(e.clientX, e.clientY, 10, 0, 2 * Math.PI);
ctx.fill()
ctx.stroke();

一些注意事项

浏览器会调用

  • canvas.onmousemove(事件相关信息),传参e

单位

  • <canvas></canvas>是HTML标签的单位同<img>

    1
    
    <canvas id='canvas' width="100" height="100"></canvas>

100vh是CSS单位

canvas.width也是HTML的属性单位,而canvas.style.width的单位是CSS样式的单位,即

1
<canvas id='canvas' width="100" height="100" style= 'width:100vh'></canvas>

手机适配

google搜js detect touch support,得判断代码

1
2
var isTouchDevice = 'ontouchstart' in document.documentElement;
console.log(isTouchDevice);

写判断

1
2
3
4
5
if (isTouchDevice === true) {
            // console.log('目前什么也不写');
        } else {
            //把PC端的代码扔进来
        }

把PC端的代码扔进else{}里后,开始写手机端代码

1
2
3
4
//监听触屏事件
canvas.ontouchmove = (e) => {
    console.log(e)
}

第一根手指

1
2
3
4
5
6
7
//监听触屏事件
canvas.ontouchmove = (e) => {
// console.log(e.touches[0]);//获取第一个手指
let x = e.touches[0].clientX;
let y = e.touches[0].clientY;
console.log(x, y)
}

后面加上PC端抄来的代码,改吧改吧

1
2
3
4
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();

发现不用监听手指抬起,比PC端更简单,特殊app内置浏览器会有向下划同步向下滚动的bug,以后处理


画线

回到PC页面,由点画线,解决不连贯的问题

从画三角,抽象一个画线的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.strokeStyle = 'none';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(500, 500);
ctx.lineTo(0, 500);
ctx.closePath();
ctx.stroke();

画线的函数

1
2
3
4
5
6
function drawLine(x1, x2, y1, y2) {
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
}

需要记录上一次的位置,每当按下鼠标后,就记录一次,修改canvas.onmousedown事件

1
2
3
4
canvas.onmousedown = (e) =>{
    painting = true;
    last = [e.clientX, e.clientY];
}

修改canvas.onmousemove事件

1
drawLine(last[0], last[1], e.clientX, e.clientY);

画完,需要每次更新last = [e.clientX, e.clientY]

1
2
drawLine(last[0], last[1], e.clientX, e.clientY);
last = [e.clientX, e.clientY];//实时更新当前位置

流程小结

  • 在最外面声明last变量
  • 再当鼠标按下时,赋值此时的点的位置坐标给last
  • 在鼠标动的时候,以last和移动后点坐标的四个值,画线
  • 更新当前点的坐标

其他细节

  • 线型变粗ctx.lineWidth
  • 线转弯处的节点有缺口,搜js line end round,得ctx.lineCap,使其圆润
    • 实验放大ctx.lineWidth = 4; drawLine(0,0,300,300); drawLine(300,300,500,200);,突然变向

别忘了改手机端

  • 手机没有onmousedown事件,取而代之的是ontouchstart事件
  • 相对应的还有ontouchstartontouchend
  • 坐标位置变为let x = e.touches[0].clientX; let y = e.touches[0].clientY;
  • 同样要记录上次位置,和更新上次位置

部署到 GitHub 上

我的项目地址

Canvas画板(第一版)预览请点击


总结

事物规律,常识抽象到代码

思维方式到了,直到搜什么

console.log()验证

不断试错


2020版前端体系课【方方】之【JS全解】Canvas 实践—画图板

方方的部署结果