0%

前端图画绘制——Canvas

作为一个假全栈开发者,遇到一个需求要在页面上显示地图,然后根据房间点击跳转,原生HTML有两种解决方法:SVG和Canvas,这两种都是在前端画图,但原理和实现完全不一样。

SVG和Canvas的区别

SVG和Canvas最大的区别是SVG是矢量图而Canvas是非矢量图,Canvas是HTML5内自带的标签,而SVG则其实并非必须基于HTML,它类似JPG、GIF,是一种定义的格式,甚至可以采用Adobe AI来画SVG。

在页面绘制方面,Canvas逻辑上是一块画布,我们在画布上通过js画我们想要的图案,因此若要做到动态效果,Canvas必须进行重绘,也就是Canvas是通过代码把每一帧写出来的方式做到的动画效果,而这样的开发也更多的依赖于前端工程师,因此Canvas更多的会用在复杂的场景上,如3D等。

而SVG则是仅采用HTML标签绘制,它也可以类似JPG一样采用image标签导入显示,SVG的动画效果采用标签animate实现,支持触发各种listener的事件,个人理解为通过HTML标签读取并执行相应js。SVG作为矢量图更多的用于一些动画渲染,比如loading图标,因为矢量且轻量,可以减少传输的数据量,又不会失真,而个人感觉SVG应该更多的是UX画的(?)。实际上我感觉SVG更像一个轻量级的Flash,因为AI里制作动画十分像Flash的模式。

Canvas基本入门

Canvas标签

Canvas在HTML部分只需要写一个标签,绘制部分全部交给js,所以必须要填id,而在标签内部,可以写一些文字,当canvas标签不支持时会显示。

1
2
3
4
<canvas id="canvasLearning">
抱歉,您的浏览器不支持canvas元素
(这些内容将会在不支持<canvas>元素的浏览器或是禁用了JavaScript的浏览器内渲染并展现)
</canvas>

Canvas坐标

Canvas坐标和大部分CS坐标一样,左上角是0,0。图片来源:Canvas入门到高级详解(上)

Canvas绘制——Context

Canvas绘制需要通过js获取canvas的上下文(context),用context来画的,2d是二维画图,也就是我们一般用的,如果用3d则是WebGL。

1
2
let canvas = document.getElementById('canvasLearning')
let ctx = canvas.getContext('2d')

一个小例子,画一个红色的正方形。
canvas画图和古老的MFC很像,首先定义画笔的颜色、粗细、样式等,也就是这里的fillStyle,然后通过函数画图,这里是画一个实心的长方形。

1
2
3
4
let canvas = document.getElementById('canvasLearning')
let ctx = canvas.getContext('2d')
ctx.fillStyle = 'rgb(200,0,0)'
ctx.fillRect(10, 10, 50, 50)

绘制基本图形

矩形

矩形有三种画法,全填充的fillRect(x, y, width, height),描边的strokeRect(x, y, width, height)和清空矩形内的画布clearRect(x, y, width, height)
这里的x和y是矩形左上角的点,也是在坐标轴内坐标最小的点。

一个例子,来自MDN

1
2
3
4
5
6
let canvas = document.getElementById('canvasLearning')
let ctx = canvas.getContext('2d')

ctx.fillRect(25, 25, 100, 100)
ctx.clearRect(45, 45, 60, 60)
ctx.strokeRect(50, 50, 50, 50)

结果如下:

路径

这里的路径是指由很多个点或线组成的图案,可以绘制空心也可绘制实心。于矩形不同的是这里会需要beginPath()作为开始绘制路径的命令,并且当使用stroke()(空心)或fill()(实心)时才会执行绘制。

移动画笔位置命令为moveTo(x, y)

下面的绘制是基于路径的绘制方法。

直线

lineTo(x, y)会从当前位置绘制一条线到坐标(x,y)

例子,绘制三角形,来自MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')

// 填充三角形
ctx.beginPath()
ctx.moveTo(25, 25)
ctx.lineTo(105, 25)
ctx.lineTo(25, 105)
ctx.fill()

// 描边三角形
ctx.beginPath()
ctx.moveTo(125, 125)
ctx.lineTo(125, 45)
ctx.lineTo(45, 125)
ctx.closePath()
ctx.stroke()

圆弧

arc(x, y, radius, startAngle, endAngle, anticlockwise)绘制一个以(x, y)为圆心,半径为radius的圆弧,圆弧的角度采用弧度制,x轴正方向为0,anticlockwise为truefalse表示顺时针或逆时针。

例子,来自MDN

1
2
3
4
5
6
7
8
9
ctx.beginPath()
ctx.arc(75, 75, 50, 0, Math.PI * 2, true)
ctx.moveTo(110, 75)
ctx.arc(75, 75, 35, 0, Math.PI, false)
ctx.moveTo(65, 65)
ctx.arc(60, 65, 5, 0, Math.PI * 2, true)
ctx.moveTo(95, 65)
ctx.arc(90, 65, 5, 0, Math.PI * 2, true)
ctx.stroke()

Style样式

最基本的就是设置绘制颜色,支持CSS中的颜色表达,命令为ctx.style = '...'

如果绘制线条,那么牵扯到线条的粗细,可以通过ctx.lineWidth设置

此外还有很多可以设置的参数,而比较特殊的我认为是渐变效果,有线性渐变createLinearGradient(x1, y1, x2, y2)和径向渐变createRadialGradient(x1, y1, r1, x2, y2, r2)

绘制文字

首先通过ctx.font设置字体,然后通过fillText(text, x, y)就可以绘制文字。

但是!这个文字很模糊,据说是绘制优化问题,它的绘制和HTML显示的绘制方法不一样,如果想要做到清晰,就需要设置一个高清画布,也就是一个实际画布大小很大,然后被缩小显示的方法。

我修改了别人的一个代码,但是找不到网址了。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function setHiDPICanvas (w, h, ratio) {
const PIXEL_RATIO = (() => {
const c = document.createElement("canvas")
let ctx = c.getContext("2d")
let dpr = window.devicePixelRatio || 1
let bsr = ctx['webkitBackingStorePixelRatio'] ||
ctx['mozBackingStorePixelRatio'] ||
ctx['msBackingStorePixelRatio'] ||
ctx['oBackingStorePixelRatio'] ||
ctx['backingStorePixelRatio'] || 1

return dpr / bsr
})()

if (!ratio) {
ratio = PIXEL_RATIO
}
let canvas = document.getElementById('canvasLearning')
canvas.width = w * ratio
canvas.height = h * ratio
canvas.style.width = w + "px"
canvas.style.height = h + "px"
canvas.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0)
}