Canvas
Canvas
<canvas> 是 HTML5 新增的,一个可以使用脚本(通常为 JavaScript) 在其中绘制图像的 HTML 元素。它可以用来制作照片集或者制作简单(也不是那么简单)的动画,甚至可以进行实时视频处理和渲染。
1.介绍
- canvas 标签通过脚本(通常是 JavaScript)来绘制图形(比如图表和其他图像)。
- canvas 标签只是图形容器,您必须使用脚本来绘制图形。
2 <canvas ref="canvasDom" />
3</template>
4
5<script>
6import { ref, onMounted, onUnmounted } from 'vue'
7
8export default {
9 setup() {
10 const canvasDom = ref()
11
12 onMounted(() => {
13 // 画布
14 const canvas = canvasDom.value
15 // 画笔
16 const ctx = canvas.value.getctx('2d')
17 onUnmounted(() => {})
18 })
19
20 return { canvasDom }
21 }
22}
23</script>
24
25<style lang="less" scoped>
26canvas {
27 background-color: #000000;
28}
29</style>
默认我们看到的是一个300px * 150px的画布
实际上canvas有两个尺寸,一个是css样式,与普通元素的宽度高度设置一致,表示<canvas>标签页面版式上的尺寸,另一个是<canvas>画布的内部尺寸,它表示的是画笔绘制的时候实际上用的尺寸
如果我们直接设置内部属性canvas.width、canvas.heigth,则外部css样式则会跟随,保证绘制效果比例一致,而如果只设置css属性尺寸,则内部尺寸不会跟随,而是拉伸内部尺寸,使内部元素填满css尺寸
考虑到元素可能需要自适应页面而不是固定,可以同时设置内外尺寸并保持一致
2import { ref, onMounted, onUnmounted } from 'vue'
3
4export default {
5 setup() {
6 const canvasDom = ref()
7
8 onMounted(() => {
9 // 画布
10 const canvas = canvasDom.value
11 // 画笔
12 const ctx = canvas.getctx('2d')
13
14 const resizeFn = () => {
15 // canvas内部尺寸变化后,需要重新绘制
16 const canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height)
17 canvas.width = canvas.getBoundingClientRect().width
18 canvas.height = canvas.getBoundingClientRect().height
19 ctx.putImageData(canvasData, 0, 0)
20 }
21 resizeFn()
22
23 window.addEventListener('resize', () => {
24 const newWidth = canvas.getBoundingClientRect().width
25 const newHeight = canvas.getBoundingClientRect().height
26 if (canvas.width !== newWidth || canvas.height !== newHeight) resizeFn()
27 })
28 onUnmounted(() => {
29 window.addEventListener('resize', resizeFn)
30 })
31 })
32
33 return { canvasDom }
34 }
35}
36</script>
37
38<style lang="less" scoped>
39canvas {
40 width: 50%;
41 height: 50%;
42 background-color: #000000;
43}
44</style>
2.基础Api
画笔方法
ctx.moveTo(x, y) 移动画笔
ctx.lineTo(x, y) 笔画停点
ctx.fill() 填充绘制
ctx.stroke() 描边绘制 绘制可同时使用
ctx.beginPath() 分次绘制 否则会重复绘制
ctx.closePath() 闭合图形 可以替代最后一笔直线
ctx.rect(x,y,width,height) 创建矩形 后面需要绘制
ctx.fillRect(x,y,width,height) 绘制填充矩形
ctx.strokeRect(x,y,width,height) 绘制描边矩形
画笔属性
lineWidth: 宽度
lineCap: 起始样式
lineJoin: 链接样式(miterLimit)
miterLimit: 斜接长度
strokeStyle: 描边样式
fillStyle: 填充样式
2.创建样式
可以将颜色值直接赋值给画笔style属性,支持透明设置
创建渐变
grd = ctx.createLinearGradient(a, b, c, d) 渐变是针对范围的,绘制的时候裁剪出来,渐变线的起点和终点不一定要在图像内,颜色断点的位置也是一样的。但是如果图像的范围大于渐变线,那么在渐变线范围之外,就会自动填充离端点最近的断点的颜色。
grd = ctx.createRadialGradient(a, b, c, d) 渐变圆
style = grd.addColorStop(stop, color) 添加渐变断点 得到的样式可以直接赋值给画笔样式style属性创建纹理
pattern = ctx.createPattern(img, repeat-style)
img对象可以是图片,canvas或者video对象,pattern可以直接赋值给画笔样式style属性
选择图片时一定要选择那种左右互通,上下互通的图片做为纹理,这样看上去才不会有不自然的短接处。还需要注意图片异步加载的时间,否则将导致绘制时找不到图片源
3.自由路径
标准圆弧
ctx.arc(x,y,radius,startAngle,endAngle,anticlockwise)
前面三个参数,分别是圆心坐标与圆半径。startAngle、endAngle使用的是弧度值,不是角度值
复杂圆弧
ctx.arcTo(x1,y1,x2,y2,radius)
5个参数,分别是两个切点的坐标和圆弧半径。这个方法是依据切线画弧线,即由两个切线确定一条弧线。
以给定的半径绘制一条弧线,圆弧的起点与当前路径的位置到(x1, y1)点的直线相切,圆弧的终点与(x1, y1)点到(x2, y2)的直线相切。
总之就是在一个角中放一个标准圆,注意一下,起点一定在路径上但不一定在圆弧上,也不一定时切点,终点不一定在路径上,也不一定时切点
ps钢笔绘制图形是基于贝塞尔曲线原理的
贝塞尔曲线原理:n阶贝塞尔曲线,链接所有的起始点与控制点(n+1个点)的n条线段上,同时取得线段上的n个x%处的点,再次链接得到n-1条线段,由此反复,直至最后一条线段上的x%处的点的轨迹连接得到的曲线
二次贝塞尔曲线
ctx.quadraticCurveTo(cpx,cpy,x,y)
二次贝塞尔曲线
三次贝塞尔曲线
ctx.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)
三次贝塞尔曲线
4.变换
变换并非变换的是图像,而是整个坐标系、整个画布
平移变换
ctx.translate(x, y)
平移x, y的距离
旋转变换
ctx.rotate(deg)
需要注意的是,这个的旋转是以坐标系的原点(0,0)为圆心进行的顺时针旋转
缩放变换
ctx.scale(sx,sy)
两个参数,分别是水平方向和垂直方向上对象的缩放倍数
- 缩放时,图像左上角坐标的位置也会对应缩放。
- 缩放时,图像线条的粗细也会对应缩放。
存储状态
ctx.save()
重置状态
ctx.restore()
1ctx.save()变换的实质是坐标变形,因为变换的状态会叠加,所以在变换之前应该保存状态并且在变换之后还原,避免坐标系错乱
2ctx.translate(x, y)
3ctx.rotate(deg)
4ctx.scale(i,i)
5ctx.strokeRect(50,50,150,100)
6ctx.restore()
矩阵变换
ctx.transform(a,b,c,d,e,f)
参数 | 意义 |
---|---|
a | 水平缩放(1) |
b | 水平倾斜(0) |
c | 垂直倾斜(0) |
d | 垂直缩放(1) |
e | 水平位移(0) |
f | 垂直位移(0) |
使用ctx.transform (1,0,0,1,dx,dy)代替ctx.translate(dx,dy)
使用ctx.transform(sx,0,0,sy,0,0)代替ctx.scale(sx, sy)
使用ctx.transform(0,b,c,0,0,0)来实现倾斜效果
使用ctx.transform(Math.cos(θMath.PI/180),Math.sin(θMath.PI/180),
-Math.sin(θMath.PI/180),Math.cos(θMath.PI/180),0,0)可以替代ctx.rotate(θ)(不好用)
ctx.setTransform(a,b,c,d,e,f)
在绘制前会自动restore
5.文本输入
画笔属性
font 设置或返回文本内容的当前字体属性
与css类似[font-style] [font-variant] [font-weight] [font-size/line-height] [font-family]
textAlign 设置或返回文本内容的当前对齐方式
textBaseline 设置或返回在绘制文本时使用的当前文本垂直对齐基线
画笔方法
ctx.fillText(String,x,y,[maxlen]) 在画布上绘制“被填充的”文本
ctx.strokeText(String,x,y,[maxlen]) 在画布上绘制文本(无填充)
fillText()与strokeText()的参数表是一样的,接受4个参数,分别是String,x,y与maxlen,其中String是指要显示的字符串,之后x与y是指显示的坐标,最后一个maxlen是可以缺省的数值型参数,代表显示的最大宽度,单位是像素。如果文本的长度超过了这个maxlen,Canvas就会将显示文本横向压缩。通常为了保证字体的美观,我们不设置maxlen。
ctx.measureText(text) 返回包含指定文本对象的宽度
因为Canvas 文本API只支持单行显示,如果文案过长需要换行显示,则需要计算文案尺寸
6.其他
画笔属性
ctx.shadowColor:阴影颜色。
ctx.shadowOffsetX:阴影x轴位移。正值向右,负值向左。
ctx.shadowOffsetY:阴影y轴位移。正值向下,负值向上。
ctx.shadowBlur:阴影模糊滤镜。数据越大,扩散程度越大。
ctx.globalAlpha:透明
ctx.globalCompositeOperation:图像合成
画笔方法
ctx.clip():裁剪区域
需要注意的是裁剪是对画布进行的,裁切后的画布不能恢复到原来的大小,也就是说画布是越切越小的,要想保证最后仍然能在canvas最初定义的大小下绘图需要注意save()和restore()。
ctx.drawImage(img,sx,sy,swidth,sheight,x,y,width,height):绘制图像
img 规定要使用的图像、画布或视频。
sx 可选。开始剪切的 x 坐标位置。
sy 可选。开始剪切的 y 坐标位置。
swidth 可选。被剪切图像的宽度。
sheight 可选。被剪切图像的高度。
x 在画布上放置图像的 x 坐标位置。
y 在画布上放置图像的 y 坐标位置。
width 可选。要使用的图像的宽度。(伸展或缩小图像)
height 可选。要使用的图像的高度。(伸展或缩小图像)
ctx.clearRect(x,y,w,h):橡皮擦,清空指定矩形上的画布上的像素
ctx.isPointInPath():判断指定的点是否在当前路径中
ImageData = ctx.createImageData():创建新的、空白的 ImageData 对象
ImageData = ctx.getImageData():返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据
ctx.putImageData(ImageData, x, y):把图像数据(从指定的 ImageData 对象)放回画布上
canvas.toDataURL():canvas保存为base64编码格式的图片
7.canvas动画
canvas动画原理就是重复绘制动画的每一帧,连续的帧就形成动画
一般会采用 requestAnimationFrame()实现动画效果。这个方法提供了更加平缓并更加有效率的方式来执行动画,当系统准备好了重绘条件的时候,才调用绘制动画帧。一般每秒钟回调函数执行60次,也有可能会被降低。
2 // 清空 canvas
3 ctx.clearRect()
4 // 保存 canvas 状态
5 ctx.save()
6 // 绘制动画图形(animated shapes)
7 ctx.[api](time)
8 // 恢复 canvas 状态
9 ctx.restore()
10 requestAnimationFrame(draw)
11}
12requestAnimationFrame(draw)
