canvas - 上
33:canvas - 上
一、介绍
- canvas 是 HTML5 新增的一个标签,表示画布
<canvas></canvas>
- canvas 也是 HTML5 的画布技术,可以通过编码的方式在画布上描绘图像
1 | <html> |
- canvas 默认是一个行内块元素
- canvas 默认画布大小是 300 * 150
- canvas 默认没有边框, 背景默认为无色透明
1.1 canvas 画布大小
- 在绘图之前, 先要确定一个画布的大小
- 因为画布默认是按照比例调整
- 所以我们调整宽度或者高度的时候, 调整一个, 另一个自然会按照比例自己调整
- 我们也可以宽高一起调整
- 调整画布大小有两种方案
- 第一种 : 通过 css 样式 ( 不推荐 )
- 第二种 : 通过标签属性 ( 推荐 )
<canvas width="1000" height="500"></canvas>
- 两种方案的区别
- 通过 css 样式的调整方案(不推荐)
- 因为 css 并没有设置了画布的大小,而是把原来 300 * 150 的画布的可视窗口变成了 1000 * 500。所以真实画布并没有放大, 只是可视程度变大了
- 如:把一个 300 * 150 的图片,放大到 1000 * 500 的大小来看
- 通过 css 样式的调整方案(不推荐)

- 通过属性的调整方案(推荐)
* 这才是真正的将画布大小调整到 1000 * 500

1.2 画布的坐标
- canvas 画布和 css 的坐标系一样。左上角为 0 0 ,向右向下延伸为正方向

二、canvas 初体验
- canvas 画布很简单,类似于 windows 电脑上的画板工具

- 在绘制之前,先选定一个形状工具(直线,矩形,圆形,...)
- 确定路径起点,落笔
- 移动到路径终点,抬笔
- 设定样式(粗细,颜色)
- 在 canvas 绘制也是一样的逻辑
- 创建一个画布工具箱
- 语法:
canvas元素.getContext('2d') - 如:
const ctx = canvasEle.getContext('2d')
- 语法:
- 确定路径起点,落笔
- 将画笔移动到一个指定位置下笔
- 语法:
工具箱.moveTo(x轴坐标, y轴坐标) ctx.moveTo(100, 100)
- 移动到路径终点,抬笔
- 将画笔移动到一个指定位置,画下一条轨迹(路径)
- 注意:这里暂时没有显示,因为只是画了一个轨迹(路径)
- 语法:
工具箱.lineTo(x轴坐标, y轴坐标) ctx.lineTo(300, 100)- 如果多个lineTo方法连续执行,表示连续绘制,即在本次抬笔的位置直接落笔。
- 设定路径样式(可在落笔之前设置)
- 语法:
工具箱.样式属性 = 样式值 - 线的宽度:
ctx.lineWidth = 10 - 线的颜色:
ctx.strokeStyle = '#000'
- 语法:
- 确定本次绘制的路径信息(生效,显现绘制效果)
ctx.stroke()- 必须绘制完成后执行
- 如:绘制一条线段:
- 创建一个画布工具箱
1 | // 0. 获取 canvas 标签元素 |
- 从坐标 ( 100, 100 ) 绘制到坐标 ( 300, 100 )
- 线段长度为 200px
- 线段宽度为 10px
- 线段颜色为 '#000' ( 黑色 )

三、canvas 线宽颜色问题
- 需要注意,在绘制任何图形时,尽量不要出现奇数宽度,因为canvas在划分了坐标点后,每次绘制都是绘制在点坐标上。
- 一个宽度为 1px 的线段就会以如下这种方式被画出来:

- canvas在描绘这个线段的时候,会把线段的最中心点放在这个像素点位上
- 也就是说,在描述线宽的时候,实际上会从 0.5px 的位置绘制到 1.5px 的位置,合计描述宽度为 1px
- 但是浏览器不能识别小数像素
- 也就是说浏览器没办法从 0.5 开始绘制,也没有办法绘制到 1.5 停止
- 那么就只能是从 0 开始绘制到 2。所以线宽就会变成 2px 了
- 又因为本身一个像素的黑色被强制拉伸到两个像素宽度,所以颜色就会变浅
- 就像我们一杯墨水, 倒在一个杯子里面就是黑色
- 但是到在一个杯子里面的时候, 又倒进去一杯水, 颜色就会变浅
- 所以最终呈现出来的样式,如下图:

- 所以,我们在进行 canvas 绘制时,涉及到线段的宽度时,一般不会把线段宽度设置成奇数,尽量设置为偶数
四、线段的开始与闭合
- 在绘制两条样式不同的独立线段时,如果直接进行绘制,后绘制的线段样式可能会影响先绘制的线段样式,如:
1 | const canvas = document.querySelector(".mycanvas"); |
- 最终呈现出下图样式

- 可以看到第二条线段的样式覆盖在了第一条线段上方
- 这是因为在绘制一条新的线段之前,需要先初始化工具箱中的样式设置。
- 也就是所谓的,准备绘制一条新的线段:
ctx.beginPath();
1 | const canvas = document.querySelector(".mycanvas"); |
- 绘制效果:

- 还可以通过绘制连续线段,组合成几何图形,如三角形:
1 | const canvas = document.querySelector(".mycanvas"); |
绘制效果:

- 注意细节:

- 当最后一条线段的终点和第一条线段的起点重复时,并没有看到一种“闭合”的效果。
- 这就需要我们主动设置连续线段的闭合:
ctx.closePath() - 只要已经绘制了至少两条连续线段,就可以使用closePath进行闭合,它会自动将最后一条线段的终点和第一条线段的起点进行连接
1 | const canvas = document.querySelector(".mycanvas"); |
- 绘制效果:

五、端点样式
- 在绘制连续线段时,canvas提供了多种线段连接点的样式处理
- 属性为:
ctx.lineJoin = '值'- 尖角:
miter(默认) - 圆角:
round - 斜角:
bevel
- 尖角:
- 属性为:
1 | const canvas = document.querySelector(".mycanvas"); |
效果如图:

- 在绘制单条线段时,线段两端的样式也可以被控制
- 属性:
ctx.lineCap = '值'- butt,无,默认
- round,圆
- square,方
- 属性:
1 | const canvas = document.querySelector(".mycanvas"); |
效果如图:

- 注意:
- square 和 round 会让线段稍稍变长
- 线段端点样式的颜色会和线段颜色保持一致
六、填充
- 当使用连续线段绘制出几何图形后,还可以对图形进行颜色填充
- 设置填充颜色:
ctx.fillStyle = '颜色值' - 填充:
ctx.fill()
- 设置填充颜色:
1 | const canvas = document.querySelector(".mycanvas"); |
效果如图:

- 注意:填充时可以不进行路径闭合,填充方法会自动闭合路径后,再进行填充
- 填充和描边可以同时使用
1 | const canvas = document.querySelector(".mycanvas"); |
效果如图:

七、canvas 的填充规则 - 非零填充(了解)
示例1
- 绘制一个 “回” 形
- 注意一个细节 :
- 里面的小正方形我们会按照 顺时针 的方向绘制
- 外面的大正方形我们也会按照 顺时针 的方向绘制
1 | // 0. 获取到页面上的 canvas 标签元素节点 |

- 填充后看效果
1 | // 4. 填充 |

- 我们发现,两个都被填充了
- 这是因为,在填充的时候,就是会一次性把所有的内容都会填充好
- 注意 :
- 和是否闭合路径 ( ctx.closePath() ) 没有关系
- 和里外正方形的绘制先后顺序没有关系
示例2
- 再绘制一个 “回” 形
- 注意一个细节:
- 里面的小正方形我们会按照 逆时针 的方向绘制
- 外面的大正方形我们也会按照 顺时针 的方向绘制
1 | // 0. 获取到页面上的 canvas 标签元素节点 |

填充后看效果:
1 | // 4. 填充 |

- 此时发现,和刚才填充出来的结果不一样了
- 可以得出结论:填充的区域和线段绘制时的 顺时针 逆时针 方向有关系!
非零填充
- 其实我们的填充和顺时针逆时针有关系,但不是简单的顺逆时针的问题
- 非零填充的概念 :
- 从任何一个区域向画布最外层移动
- 按照经历最少的边数量计算
- 其中经历的顺时针边,记录为 +1
- 经历的逆时针边,记录为 -1
- 只要最终总和不为 零,那么该区域填充
- 如果最终总和为 零,那么该区域不填充
示例3:
- 这次我们绘制一个稍微复杂一些的图形

- 这是两个矩形对接在一起, 一个是顺时针绘制, 一个是逆时针绘制
- 我们来分析一下看看
- 首先, 最左侧封闭图形区域

- 如果走最短的路线出来的话,会经历一条顺时针的边
- 记录为 +1
- 最终为 +1
- 所以该区域会被填充
- 然后, 最右侧封闭图形

- 经历最短路线出来的话,会经历一条逆时针的边
- 记录为 -1
- 最终为 -1
- 所以该区域会被填充
- 最后, 中间的封闭图形

- 经历最短路线出来的话,必然会经历一条顺时针的边 和 一条逆时针的边
- 顺时针记录为 +1
- 逆时针记录为 -1
- 合计为 0
- 所以该区域不会被填充
- 实际测试
1 | // 0. 获取到页面上的 canvas 标签元素节点 |
- 效果如图:

八、绘制矩形
- 绘制矩形的方法有三个:
- 矩形路径:
- 语法:
ctx.rect( 矩形起点 x 轴坐标, 矩形起点 y 轴坐标, 矩形宽度, 矩形高度 ) - 如:
ctx.rect(100, 100, 100, 100) - 表示在坐标 100,100 的位置绘制一个 100*100 的矩形路径,默认无填充无描边
- 可通过
ctx.stroke()描边,通过ctx.fill()填充 - 可通过
lineWidth,strokeStyle,fillStyle属性,分别设置描边宽度,描边色,填充色
- 语法:
- 描边矩形:
- 语法:
ctx.strokeRect( 矩形起点 x 轴坐标, 矩形起点 y 轴坐标, 矩形宽度, 矩形高度 ) - 如:
ctx.strokeRect(300, 100, 100, 100) - 表示在坐标 300,100 的位置绘制一个 100*100 的描边矩形,无填充
- 可通过
lineWidth,strokeStyle属性,设置描边宽度,描边色
- 语法:
- 填充矩形:
- 语法:
ctx.fillRect( 矩形起点 x 轴坐标, 矩形起点 y 轴坐标, 矩形宽度, 矩形高度 ) - 如:
ctx.fillRect(500, 100, 100, 100) - 表示在坐标 500,100 的位置绘制一个 100*100 的填充矩形,无描边
- 可通过
fillStyle属性,设置填充色
- 语法:
1 | // 0. 获取到页面上的 canvas 标签元素节点 |
效果如图:

九、绘制圆形
- 什么是圆:
- 圆 就是从一个点出发,按照半径,画弧线,当弧线饶了一圈回到原点的时候,就是一个圆形。
- 在canvas内,绘制圆形,其实就是在绘制弧线
- 什么是弧度
- 这是一个圆,圆心为 o,半径为 r

- 以圆心 o 做坐标轴,x 轴正方向上和圆周的交点为弧度起点

- 在圆周上,从弧度起点,顺着圆周移动,移动的距离成为弧长,当弧长和半径一样时
- 这段弧长所对应的圆心角是 1 弧度

- 根据圆周公式 : 周长 = 2 * π * r
- 所以:
* 一个圆周是 : 2 * π
* 半个圆周是 : π
* 四分之一圆周是 : π / 2
- 了解了什么是弧度,接下来就可以开始绘制弧线了,绘制弧线有两种方式:圆弧,椭圆弧
- 圆弧
- 语法:
ctx.arc( x, y, r, startAngle, endAngle, counterclockwise )- x:圆心的 x 轴坐标
- y:圆心的 y 周坐标
- r:圆的半径
- startAngle:绘制弧线的起点弧度
- endAngle:绘制弧线的终点弧度
- counterclockwise:方向,false 为顺时针(默认),true 为逆时针
- 如:
ctx.arc( 150, 150, 100, 0, 1, false ) - 表示绘制一个圆心在坐标 150,150,半径100,从 0 顺时针 到 1 的弧线路径,默认无填充无描边
- 语法:
- 圆弧
1 | // 0. 获取到页面上的 canvas 标签元素节点 |

- 椭圆弧
* 语法:`ctx.ellipse( x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise )`
+ x:椭圆中心点的 x 轴坐标
+ y:椭圆中心点的 y 轴坐标
+ radiusX:椭圆在 x 轴方向上的半径
+ radiusY:椭圆在 y 轴方向上的半径
+ rotation:旋转弧度,指讲该椭圆进行旋转
+ startAngle:弧线开始弧度
+ endAngle:弧线结束弧度
+ antiClockwise:方向,false 表示逆时针绘制(默认),true 表示顺时针绘制
* 如:`ctx.ellipse( 300, 150, 200, 100, 0, 0, Math.PI * 2, false )`
* 表示绘制一个圆心在坐标 350,150,x轴半径200,y轴半径为100,不旋转,从 0 顺时针 到 Math.PI * 2 的弧线路径,默认无填充无描边
1 | // 0. 获取到页面上的 canvas 标签元素节点 |

* 这样一个椭圆就出来了,解释一下这些内容的意义

* 旋转弧度,就是在现在的基础上,让整个图形进行旋转
1 | ctx.ellipse( 300, 150, 200, 100, Math.PI / 2, 0, Math.PI * 2, false ) |

十、擦除画布
- 就像画画时的橡皮擦,擦除掉指定区域的内容
- 语法:
工具箱.clearRect( 矩形起点 x 轴坐标, 矩形起点 y 轴坐标, 矩形宽度, 矩形高度 )- 如:
工具箱.clearRect( 150, 150, 30, 30 ) - 表示从坐标 150, 150 位置开始,擦除一块 30 * 30 的区域
- 如:

- 注意:
- clearRect默认只能擦除填充和描边,并不能擦除路径
- canvas中的绘制方法(如stroke,fill),会以“上一次 beginPath 之后的所有路径为基础进行绘制
- 如果没有使用beginPath()方法,上一次描述的路径没有被清除,这一次进行描边等操作还会绘制出之前的路径,表现出一种类似没有擦除的状态。
- 所以为了彻底擦除,在使用了clearRect后,一般都会再执行一次beginPath方法
十一、绘制文字
- 在canvas内可以直接绘制文字,不需要通过线段一笔一划的写出来
- 描边文字(空心):
- 语法:
ctx.strokeText("文字内容", x 坐标, y 坐标);
- 语法:
- 填充文字(实心):
- 语法:
ctx.fillText("文字内容", x 坐标, y 坐标);
- 语法:
- 描边文字(空心):
1 | // 0. 获取到页面上的 canvas 标签元素节点 |

- 文字样式修饰
- 字体大小:
ctx.font = '字体大小 字体' - 文字水平对齐:
ctx.textAlign = 'left | center | right'; - 文字垂直对齐:
ctx.textBaseline = 'top | middle | bottom'; - 注意:文字的样式修饰需要在绘制之前设置
- 字体大小:
1 | // 0. 获取到页面上的 canvas 标签元素节点 |

| 水平对齐:ctx.textAlign | 垂直对齐:ctx.textBaseline |
|---|---|
| start:文本在指定的位置开始 end:文本在指定的位置结束 center:居中对齐 left:左对齐 right:右对齐 |
alphabetic:默认,文本基线是普通的字母基线 top:文本基线是 em 方框的顶端 hanging:文本基线是悬挂基线 middle:文本基线是 em 方框的正中 ideographic:文本基线是 em 基线 bottom:文本基线是 em 方框的底端 |
![]() |
![]() |
- 获取文本信息
- 语法:
ctx.measureText("文本") - 获取文本宽度:
ctx.measureText("前端").width - 用于给文本添加下划线,或边框线等操作
- 语法:
十二、阴影
- 在canvas中,还可以对路径添加阴影效果(文字也可以有阴影)
- 阴影 x 轴偏移:
ctx.shadowOffsetX = number; - 阴影 y 轴偏移:
ctx.shadowOffsetY = number; - 模糊大小:
ctx.shadowBlur = number; - 阴影颜色:
ctx.shadowColor = '颜色值';
- 阴影 x 轴偏移:
1 | // 0. 获取到页面上的 canvas 标签元素节点 |
- 如需不同的阴影效果,每次绘制前可以重新配置
十三、绘制虚线
- 语法:
工具箱.setLineDash([ 第一段长度, 第二段长度, ... ]) - 如:
ctx.setLineDash([5, 10]) - 表示绘制出的虚线为:实5,虚10,实5,虚10,……
1 | // 0. 获取到页面上的 canvas 标签元素节点 |
注意:填充无法设置虚线效果
十四、总结
- 创建一个画笔对象(获取工具箱)
const ctx = canvasElement.getContext('2d');
- 常用属性
- 线条粗细:
ctx.lineWidth = number; - 描边色:
ctx.strokeStyle = '颜色值'; - 端点样式:
ctx.lineCap = 'butt | round | square'; - 接洽点样式:
ctx.lineJoin = 'miter | bevel | round'; - 填充色:
ctx.fillStyle = '颜色值'; - 字体大小:
ctx.font = '字体大小 字体'; - 文字水平对齐:
ctx.textAlign = 'left | center | right'; - 文字垂直对齐:
ctx.textBaseline = 'top | middle | bottom'; - 阴影 x 轴偏移:
ctx.shadowOffsetX = number; - 阴影 y 轴偏移:
ctx.shadowOffsetY = number; - 模糊大小:
ctx.shadowBlur = number; - 阴影颜色:
ctx.shadowColor = '颜色值';
- 线条粗细:
- 常用方法
- 下次绘制开启新路径(弃用已存在路径):
ctx.beginPath(); - 开始绘制线段的起点:
ctx.moveTo(x, y); - 连线到:
ctx.lineTo(x, y); - 闭合路径:
ctx.closePath(); - 描边:
ctx.stroke(); - 填充:
ctx.fill(); - 矩形路径:
ctx.rect(x, y, w, h); - 描边矩形:
ctx.strokeRect(x, y, w, h); - 填充矩形:
ctx.fillRect(x, y, w, h); - 圆弧路径:
ctx.arc(cx, cy, r, start, end, false); - 椭圆弧路径:
ctx.ellipse( cx, cy, xr, yr, rotate, start, end, false ); - 擦除:
ctx.clearRect(x, y, w, h); - 填充文字:
ctx.fillText(string, x, y); - 描边文字:
ctx.strokeText(string, x, y); - 获取文字信息:
ctx.measureText('测试'); - 设置虚线:
setLineDash([线段1宽度, 线段2宽度, ...])
- 下次绘制开启新路径(弃用已存在路径):
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 !
评论


