你不知道的 canvas(一)
一、前言
hello 大家好~ 。今天给大家介绍一个在前端被广泛运用的东西——canvas,canvas 在动画、游戏画面、数据可视化、图片编辑、实时视频处理等方面都扮演着一个非常重要的角色。常见的 canvas 制作 H5 小游戏,还有应用比较广泛的 echarts 图表也是由 canvas 绘制而成的,诸如此类的图表有很多很多。所以说 canvas 是一个有趣、运用广泛、功能强大的一个东西。
二、什么是 canvas?
canvas 是 html 的一个标签,它可以结合 JavaScript 提供的 canvasApi 来绘制各种各样的图形。canvas 主要用于绘制 2D 图形。注意:当你不设置 canvas 的宽高时,它的默认宽高是 300px、150px,而且当通过 CSS 样式设置 canvas 宽高而不是直接通过 width、height 属性设置宽高时图形会发生伸缩。
canvas 的简单使用(绘制一个红色的长方形)
<canvas id="canvas"></canvas>
<script>
// 获取 canvas 元素的引用
let canvas = document.getElementById('canvas')
// 获取 canvas 元素的 context,图形将绘制在元素的 context 上。
let context = canvas.getContext('2d')
// 填充颜色
context.fillStyle = 'red'
// 绘制长方形 参数:相对于浏览器左上角的坐标位置 x,y 以及矩形的宽高四个参数
context.fillRect(0, 0, 200, 100)
</script>
三、canvas 的预留内容
当在不支持 canvas 标签的浏览器中,canvas 开始标签和结束标签之间的内容会被显现出来。注意:canvas 必须含有结束标签,没有结束标签虽然不会报错但是文档的其他内容会被当做预留内容。
<!-- 不支持 canvas 的浏览器将会显示标签内的内容 -->
<canvas>当前浏览器不支持canvas</canvas>
四、绘制形状
通过以上的简单例子我们已经学会了如何开始绘制一个图形,接下来我们来了解其他形状的绘制。
4.1位置坐标概念
在以上的例子中我们用到了坐标位置 x,y 注意该坐标位置相对于 canvas 的左上角来设定的(水平方向为 x 垂直方向为 y),而不是整个文档的左上角。
4.2绘制矩形
<body>
<canvas id="canvas"></canvas>
<script>
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d')
// 绘制相对于画布左上角坐标为(x, y)->(50px, 50px) 且宽高都为 100px 的填充矩形。
ctx.fillRect(50,50,100,100)
// 清除一个指定的矩形区域,将该区域变为透明。清除的区域起点为(60px, 60px),宽高为 80px 的区域。
ctx.clearRect(60, 60, 80, 80)
// 绘制相对于画布左上角坐标为(x, y)->(75px, 75px) 且宽高都为 50px 的矩形边框。
ctx.strokeRect(75 ,75 , 50, 50)
// 绘制相对于画布左上角坐标为(x, y)->(160px, 50px) 且宽高都分别为 90px、100px 的矩形边框。
ctx.rect(160, 50, 90 , 100)
ctx.stroke()
</script>
</body>
4.3绘制路径
路径是图形的基本元素,路径是由不同颜色、宽度的线段或曲线相连形成不同形状的点的集合。
绘制路径的步骤:
- 创建路径的起点 -> 使用 beginPath() 新建一条路径,结束前一条路径的绘制。
- 使用画图方法画出路径
- 封闭路径 -> 使用 closePath() 封闭路径。fill() 也会自动封闭路径,但 stroke() 不会
- 封闭路径后就可以通过描边或填充路径区域来进行图形渲染。-> stroke() 绘制图形轮廓,fill() 填充路径形成的区域。
<!-- 绘制一个三角形 -->
<body>
<canvas id="canvas"></canvas>
<script>
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
// 创建一条路径
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(80, 80)
ctx.lineTo(20, 80)
ctx.fill()
</script>
</body>
以上案例涉及到了两个很有用的方法:moveTo() 、 lineTo():
- moveTo(x, y): 它不会绘制任何图形,它的作用是改变画笔笔尖的落点,将笔尖从当前位置移到指定的(x,y)坐标点。
- lineTo(x, y): 用以绘制一条从当前位置到(x, y)点的直线。
stroke() 与 fill() 区别,再来看个例子。
以下案例绘制了两个三角形来测试 fill() 和 stroke() 的区别,第一前面已经提到过 fill() 用来填充路径区域,stroke() 用来描边。第二 fill() 会自动关闭路径所以会形成以下填充好颜色的三角形,然而 stroke() 不会自动关闭路径,所以形成了一个不完整的三角形,需要手动 closePath() 后才会绘制一个完整的三角形。注意:关闭路径不是结束一条路径的绘制,结束一条路径的绘制需要开启一条新的路径(beginPath)。
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
// 填充三角形
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(80, 80);
ctx.lineTo(20, 80);
ctx.fill();
// 不完整描边三角形
ctx.beginPath();
ctx.moveTo(120, 50);
ctx.lineTo(90, 80);
ctx.lineTo(150, 80);
// ctx.closePath();
ctx.stroke();
4.4 绘制圆弧
使用 arc() 绘制圆弧或者圆,它接受 6 个参数, arc(x, y, radius, startAngle, endAngle, anticlockwise) 表示以 x, y 为圆心 radius 为半径,起始弧度 startAngle,结束弧度为 endAngle 按照给定的方向 anticlockwise (anticlockwise 值为布尔值 true 则按逆时针绘制, false 则按顺时针绘制,默认为 false) 绘制一个圆弧或者圆。一个圆的弧度为 2π(js表示为:2*Math.PI),相信大家都知道,那么 startAngle、endAngle 的取值就大家的需求而定了。
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
// 绘制弧度为 π/2 的圆弧
ctx.arc(50, 50, 30, 0, Math.PI/2, false)
ctx.stroke();
ctx.beginPath()
// 绘制一个圆
ctx.arc(150, 50, 30, 0, Math.PI*2, false)
ctx.stroke();
4.5 贝塞尔曲线绘制
我们这里主要介绍二次贝塞尔曲线和三次贝塞尔曲线,它们都是用于绘制曲线,以实现绘制复杂的图形。二次贝塞尔曲线由三个点控制绘制一条曲线。三次贝塞尔曲线由四个点控制绘制一条曲线。
更加深入了解贝塞尔曲线戳这里
- 二次贝塞尔曲线
简单的二次贝塞尔曲线绘制
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
ctx.moveTo(50, 150)
ctx.quadraticCurveTo(200, 25, 350, 150)
ctx.stroke()
二次贝塞尔曲线是由三个点控制的,如下图,两个红色的点分别代表曲线的起点和终点,绿色的点是控制点,在起点终点不变的情况下控制点不同曲线的弯曲程度不同。曲线的绘制路径原理请参考上面我推荐的关于贝塞尔曲线的文章。
- 三次贝塞尔曲线
三次贝塞尔曲线在二次贝塞尔曲线的基础上另增加了一个控制点,控制点越多绘制会越复杂,最终效果是比较难预测的,所以需要一次次尝试才会达到预期的效果。
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
ctx.moveTo(50, 150)
ctx.bezierCurveTo(100, 25, 300, 25, 350, 150)
ctx.stroke()
下图两个红点代表两个控制点
4.6 Path2D 对象
为了简化代码和提高性能,在比较新的浏览器可以使用 Path2D 创建的路径在 canvas 上来绘制各种各样的图形 ,Path2D 创建的对象可以缓存绘制命令。Path2D() 会返回一个新的初始化的 Path2D 对象,也可以接受一个路径作为参数或 接受一个 SVG path 字符串。
以上提到绘制方法在 Path2D 中大多都有实现,打印 Path2D 对象结果如下:
- 使用 Path2D 绘制矩形
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d')
// 绘制矩形
let pathObj = new Path2D()
pathObj.rect(10, 10, 100, 50)
ctx.stroke(pathObj)
- Path2D 参数接受一个路径,在 pathObj 路径上接着画一个圆。而且 Path2D 缓存绘制路径的功能也体现出来了,pathObj 可以随时被拿来使用。
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d')
let pathObj = new Path2D()
pathObj.rect(10, 10, 100, 50)
let pathObj2 = new Path2D(pathObj)
pathObj2.arc(140, 25, 20, 0, 2*Math.PI)
ctx.stroke(pathObj2)
- Path2D 参数接受一个矢量路径字符串, 例:"M20 20 h 100 v 100 h -100 Z", (M20 20)表示的是将路径的起点移至(20, 20),(h 100) 表示水平向右移动 100 像素,(v 100) 表示垂直向下移动 100 像素,(h -100) 表示水平向左移动 100 像素, (Z) 表示回到起点处。
矢量路径绘制正方形
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d')
let pathObj = new Path2D('M20 20 h 100 v 100 h -100 Z')
ctx.stroke(pathObj)
五、样式和颜色
5.1 颜色样式
到此为止,以上内容我们主要讲了内容的绘制方法,如果我们想要给绘制的内容添加样式该通过什么方法呢,主要使用 fillStyle(设置填充颜色) 和 strokeStyle(设置路径颜色) 两个属性设置绘制颜色。它们的默认值都为 #000,所以以上绘制的图形填充色和轮廓颜色都为黑色。
绘制一个填充色为红色的矩形,在调用 fill() 之前设置 fillStyle()。
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d')
ctx.rect(10, 10, 100, 50)
ctx.fillStyle = 'red'
ctx.fill()
绘制一个边框轮廓为蓝色的矩形
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d')
ctx.rect(10, 10, 100, 50)
ctx.strokeStyle = 'blue'
ctx.stroke()
5.2 透明度
通过 RGBA 和 globalAlpha 两种方式可以设置颜色的透明度。RGBA 需要符合 CSS3 颜色规范的值。globalAlpha 的值为 0 ~ 1 之间。
- 使用 RGBA 绘制背景色为红色透明度 50% 的矩形。
ctx.rect(10, 10, 100, 50)
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'
ctx.fill()
- 使用 globalAlpha 设置透明度.
ctx.rect(10, 10, 100, 50)
ctx.fillStyle = 'red'
ctx.globalAlpha = 0.5
ctx.fill()
5.3 线条样式
- 设置线的宽度 lineWidth, 值必须为正数,默认值为 1。
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d')
ctx.moveTo(10, 10)
ctx.lineWidth = 10
ctx.lineTo(10, 80)
ctx.stroke()
- 设置线条端点的样式 lineGap, lineGap 有三个取值:butt、round、square,默认值为 butt
根据 lineGap 的三个不同取值绘制三条线条,如下图,中间的 lineGap 值为 round 左边的为 butt 右边的为 square。其中两条蓝色的两条线为辅助线,让三条垂直线条的区别更加明显。它们的区别主要体现在两个方面:
- butt 和 square 的端点是直角拐点,round 的端点是一个圆弧
- 在起点、终点都在辅助线上的情况下,round 和 square 会在原来的起点和终点的基础上分别延伸半个线条宽度的距离,round 延伸的是直径为线宽的半圆,square 延伸的是一个宽为线宽高为线宽一半的矩形。
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
ctx.beginPath()
ctx.strokeStyle = 'blue';
ctx.moveTo(10, 10)
ctx.lineTo(150, 10)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(10, 120)
ctx.lineTo(150, 120)
ctx.stroke()
function drawLine(){
let tyleList = ['butt','round','square'];
ctx.strokeStyle = '#000';
for (var i=0;i<3;i++){
ctx.lineWidth = 15;
ctx.lineCap = tyleList[i];
ctx.beginPath();
ctx.moveTo(25 + i*50, 10);
ctx.lineTo(25 + i*50, 120);
ctx.stroke();
}
}
drawLine()
!
- lineJoin 设置线段拐点处的样式。它有三个取值:round、bevel、miter(默认值)。
根据 lineJoin 的不同取值绘制三条折线线段。如下图,最上面的路径 lineJoin 值为 round,中间的为 bevel,最下面的为 miter。红色的圆为辅助圆,直观的表现出 round 的连接点外侧为一个圆弧。
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
function drawLine(){
var lineJoin = ['round', 'bevel', 'miter'];
ctx.lineWidth = 10;
for (var i = 0; i < lineJoin.length; i++) {
ctx.lineJoin = lineJoin[i];
ctx.beginPath();
ctx.moveTo(-5, 5 + i * 40);
ctx.lineTo(35, 45 + i * 40);
ctx.lineTo(75, 5 + i * 40);
ctx.lineTo(115, 45 + i * 40);
ctx.lineTo(155, 5 + i * 40);
ctx.stroke();
}
}
drawLine()
ctx.beginPath()
ctx.arc(35, 45, 5, 0, Math.PI*2)
ctx.fillStyle = 'red'
ctx.fill()
- 虚线绘制,涉及到 setLineDash 方法和 lineDashOffset 属性。setLinedash 接受一个数组用于设置虚线线段和间隙的宽度,lineDashOffset 用于设置相对于虚线初始位置的偏移量。
绘制边框为虚线的矩形
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
function drawDash(){
ctx.setLineDash([4, 2])
ctx.lineDashOffset = 0
ctx.strokeRect(10, 10, 100, 40)
ctx.setLineDash([8, 4])
ctx.lineDashOffset = 0
ctx.strokeRect(10, 60, 100, 40)
}
drawDash()
5.4 颜色渐变
我们可以使用线性渐变或径向渐变来进行填充或描边。通过新建 canvasGradient 对象,并将该对象赋值给 fillStyle 和 strokeStyle 属性。
- 线性渐变
通过 createLinearGradient(x1, y1, x2, y2) 创建 canvasGradient 线性渐变对象,(x1, y1) 为渐变的起点,(x2, y2) 为渐变的终点。创建了 canvasGradient 对象后,可以通过 addColorStop(position, color) 指定在渐变区域某个相对位置(position)的渐变颜色(color), position 的值为 0 ~ 1 之间(例如:当值为0.5时表示在渐变区域的中间位置),color 值为 CSS 中颜色的有效值。
绘制一个从上到下填充色由红变粉色的矩形
let lineargradient = ctx.createLinearGradient(10, 10 , 10, 100)
lineargradient.addColorStop(0, 'red')
lineargradient.addColorStop(1, 'pink')
ctx.rect(10, 10, 200, 100)
ctx.fillStyle = lineargradient
ctx.fill()
- 径向渐变
通过 createRadialGradient(x1, y1, r1, x2, y2, r2),创建 canvasGradient 径向渐变对象,前三个参数定义了一个 (x1, y1) 为原点的圆, r1 为半径,后三个参数 定义了另一个(x2, y2) 为原点, r2 为半径的圆。
绘制由内向外填充色渐变的圆
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
let radialGradient = ctx.createRadialGradient(50, 50, 10, 50, 50, 30)
radialGradient.addColorStop(0, 'red');
radialGradient.addColorStop(0.9, 'blue');
radialGradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
ctx.rect(10, 10, 200, 100)
ctx.fillStyle = radialGradient
ctx.fill()
5.5 图案样式
可以通过 createPattern() 实现图案的效果,它接受两个参数第一个参数可以是 Image 对象或 canvas 对象,第二个参数的取值可以是:repeat、repeat-x、repeat-y、no-repeat。
通过一个图片绘制一个图案
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
let img = new Image()
img.src = './atom.png'
img.onload = function(){
let pat = ctx.createPattern(img, 'repeat')
ctx.fillStyle = pat;
ctx.rect(10, 10, 200, 100)
ctx.fill()
}
参数为 canvas 对象
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
ctx.arc(5, 5, 5, 0, 2*Math.PI)
ctx.fill()
// 以一个 canvas 对象绘制图案
let canvas2 = document.getElementById('canvas2')
let ctx2 = canvas2.getContext('2d');
ctx2.fillStyle = ctx2.createPattern(canvas, 'repeat')
ctx2.fillRect(10, 10, 100, 50)
5.6 阴影
通过设置 shadowOffsetX、shadowOffsetY 属性的值设置阴影的延伸距离,默认都为 0,为正数时分别表示设置向右和向下的阴影距离,负数表示向左和向上的阴影距离。shadowBlur 属性用于设置阴影的模糊程度,默认值为 0,值越大越模糊。shadowColor 属性用于设置阴影的颜色,默认值为全透明黑色。
设置一个圆的阴影
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
ctx.shadowOffsetX = 10
ctx.shadowBlur = 10
ctx.shadowColor = 'red'
ctx.arc(50, 50, 30, 0, 2*Math.PI)
ctx.fill()
5.7 canvas 填充规则
当使用 fill() 进行填充时,有两个填充规则可选 nonzero(非零规则)、evenodd(偶数奇数规则)。默认值为 nonzero。
关于两种规则的判定方法:戳这里
两个重叠矩形的 evenodd 的填充规则
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
ctx.fillStyle = 'red'
ctx.rect(10, 10, 100, 50)
ctx.rect(20, 20, 80, 30)
ctx.fill('evenodd')
六、绘制文本
6.1 绘制文本
- fillText(text, x, y [,maxWidth])
它可以接受四个参数,表示将文本(text)绘制在位置坐标为(x, y)的位置上,文本的最大宽度(maxWidth)可选
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
ctx.font = '30px SimSun'
ctx.fillText('hello canvas!', 40, 40)
- strokeText(text, x, y [,maxWidth])
参数的含义和 fillText 一致,区别在于 strokeText 绘制的文本是带有边框的
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
ctx.font = '30px SimSun'
ctx.strokeText('hello canvas!', 40, 40)
6.2 文本样式属性
- font
默认字体样式为 '10px sans-serif',可用于设置字体的大小、种类等
-
textAlign, 可选值:start(默认值) | end | left | right | center, 设置文本的对齐方式。
-
textBaseLine,可选值:top | hanging | middle | alphabetic(默认值) | ideographic | bottom,文字基准对齐方式。
-
direction,可选值:ltr(文本方向从左到右) | rtl(文本方向从右到左) | inherit(默认值),设置文本的方向
6.3 测量文本的宽度
measureText(text)
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
let text = ctx.measureText('hello canvas!')
console.dir(text);
七、最后
由于 canvas 涉及到的内容比较多,写在一篇文章里的话文章的篇幅会比较长,故 canvas 的剩余知识点我打算放在第二篇文章,也有可能还会有第三篇。后面会持续更新,未完待续~