使用canvas 绘制图形
上一篇 canvas基本用法
在学习了canvas基本用法 我们开始着手在 canvas 上绘制矩形、三角形、直线、圆弧和曲线。
栅格

在开始在 canvas 上面绘制图形之前需要了解一下画布栅格以及坐标空间。如上图的,canvas 元素默认被网格所覆盖,每一个单元格代表一个像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。
绘制矩形
不同于SVG,HTML中的元素 canvas 只支持一种原生的图形绘制:矩形。其他的图形绘制都至少需要生成一条路径。
canvas 提供了三种方法绘制矩形:
fillRect(x,y,width,height)
绘制一个填充的矩形
strokeRect(x,y,width,height)
绘制一个矩形的边框
clearRect(x,y,width,height)
清除指定矩形区域,让清除部分完全透明
上面提供的方法中包含相同的参数,x/y指定 所绘制的图形相对于原点的坐标,width/height设置了矩形的宽高
绘制矩形 例子
function draw(){
let canvas = document.getElementById('canvas')
if(canvas.getContext()){
let ctx = canvas.getContext('2d')
ctx.fillRect(25,25,100,100)
ctx.clearRect(45,45,60,60)
ctx.strokeRect(50,50,50,50)
}
}
输出如下图:

fillRect()
函数绘制了一个边长为100px的黑色正方形。clearRect()
函数从中正方形的中心位置擦出了一个60x60的正方形,接着 strokeRect()
在清除区内生成一个50*50的正方形边框
绘制路径
canvas 只支持一种原生的图形绘制:矩形。其他的图形绘制都至少需要生成一条路径。 图形的基本元素是路径,路径是通过不同的颜色和宽度的线段或者曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的,使用路径绘制图形需要一些额外的步骤。
- 首先,需要创建路径的起点
- 然后使用画图命令去画出路径
- 把路径封闭
- 一旦路径生成,就能通过描边或者填充路径区域来渲染图形。
以下是所用到的函数:
beginPath()
新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
moveTo()
将笔触移动到指定的坐标上面,通常当canvas初始化或者beginPath()
调用后,需要使用moveTo()
函数设置起点。
closePath()
闭合路径之后图形绘制命令又重新指向到上下文中。
stroke()
通过线条来绘制图形轮廓
fill()
通过填充路径的内容区域生成实心的图形。
生成路径的第一步叫做 beginPath()
。本质上,路径是有很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧线)构成图形。而每次这个方法调用之后,列表清空重置,然后就可以重新绘制新的图形。
第二部就是调用函数指定绘制路径。
第三步就是闭合路径closePath()
不是必须的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形已经是闭合的,即当前点为开始点,该函数什么也不做。
注意:当调用fill()函数时,所有没有闭合的形状都会自动闭合,所以不再需要调用closePath()函数,但是调用stroke()时不会自动闭合
绘制一个三角形
function draw(){
let canvas = document.getElementById('canvas')
if(canvas.getContext()){
let ctx = canvas.getContext('2d')
//开始一个路径
ctx.brginPath()
ctx.moveTo(75,50)
ctx.lineTo(100,75)
ctx.lineTo(100,25)
ctx.fill()
}
}
输出图形:

移动触笔
一个非常有效的函数,而这个函数实际上并不能画出任何东西,这个函数就是`moveTo()`。是一个点到另一个点的移动过程。
moveTo(x,y)
将笔触移动到指定的x,y的坐标上面。
当canvas初始化或者beginPath()
调用后,通常会使用moveTo()
函数设置起点。或者也可以用moveTo()
绘制一些不连续的路径。
function draw(){
let canvas = document.getElementById('canvas')
if(canvas.getContext()){
let ctx = canvas.getContext('2d')
//开始绘制
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();
}
}
效果图:

线
绘制直线,需要用到的方法lineTo()
。
lineTo()
绘制一条从当前位置到指定坐标(x,y)的直线。
该方法有两个参数,x,y代表坐标系中直线结束的点。开始点是上个路径的结束点,或者使用moveTo()
来设置开始点。
function draw(){
let canvas = document.getElementById('canvas')
if(canvas.getContext()){
let ctx = canvas.getContext('2d')
//填充三角形
ctx.beinPath();
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()
}
}

这里从调用beginPath()
函数准备绘制一个新的路径,然后使用moveTo()
函数移动到目标位置上。
填充与描边不厚有所不同。路径使用
fill()
填充时,路径会自动闭合,而使用stroke()
描边时,路径则不会自动闭合,需要使用closePath()
来闭合线路。
圆弧
绘制圆弧或者圆,使用arc()
方法。也可以使用arcTo()
。
arc(x,y,radius,startAngle,endAngle,anticlockwise)
画出一个以(x,y)为圆心,radius为半径的圆弧,从startAngle开始到endAngle结束,按照anticlockwise的方向(默认为true顺时针,false为逆时针)。
arcTo(x1,y1,x2,y2,radius)
根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点。
startAngle和endAngle参数定义了开始以及结束的弧度,并且以x轴为基准。一个正圆的弧度为Math.PI*2,半圆的弧度为Math.PI
function draw(){
let canvas = document.getElementById('canvas')
if(canvas.getContext()){
let ctx = canvas.getContext('2d')
for(let i = 0;i<4;i++){
for(let j = 0;j<4;j++){
ctx.beginPath();
var x = 25+j*50; // x 坐标值
var y = 25+i*50; // y 坐标值
var radius = 20; // 圆弧半径
var startAngle = 0; // 开始点
var endAngle = Math.PI+(Math.PI*j)/2; // 结束点
var anticlockwise = i%2==0 ? false : true; // 顺时针或逆时针
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
if (i>1){
ctx.fill();
} else {
ctx.stroke();
}
}
}
}
}

二次贝赛尔曲线以及三次贝赛尔曲线
一般用于绘制复杂有规律的圆弧
quadraticCurveTo(cp1x,cp1y,x,y)
绘制二次贝赛尔曲线,cpx1,cpy1为一个控制点,x,y为结束点。
bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)
绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。

上图描述了二次贝赛尔曲线和三次贝赛尔曲线的关系。二次贝赛尔曲线有一个结束点(蓝色)以及一个控制点(红色),而三次贝塞尔曲线有两个控制点,一个结束点;
参数x,y为结束点的坐标,cp1x,cp1y为第一个控制点的坐标,cp2x,cp2y为第二个控制点的坐标。
二次贝赛尔曲线 图例
用二次贝赛尔曲线渲染一个对话气泡。
function draw(){
let canvas = document.getElementById('canvas')
if(canvas.getContext()){
let ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.noveTo(75,75)
ctx.quadraticCurveTo(25,25,25,62.5)
ctx.quadraticCurveTo(25,100,50,100);
ctx.quadraticCurveTo(50,120,30,125);
ctx.quadraticCurveTo(60,120,65,100);
ctx.quadraticCurveTo(125,100,125,62.5);
ctx.quadraticCurveTo(125,25,75,25);
ctx.stroke()
}
}

三次贝赛尔曲线 图例
使用三次贝赛尔曲线绘制一个心形的图案
function(){
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
//三次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(75,40);
ctx.bezierCurveTo(75,37,70,25,50,25);
ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
ctx.bezierCurveTo(20,80,40,102,75,120);
ctx.bezierCurveTo(110,102,130,80,130,62.5);
ctx.bezierCurveTo(130,62.5,130,25,100,25);
ctx.bezierCurveTo(85,25,75,37,75,40);
ctx.fill();
}
}

矩形
直接在画布上绘制矩形除去fillRect()
,strokeRect()
,clearRect()
外,也可以使用rect()
方法讲一个矩形路径添加到当前路径上面。
rect(x,y,width,height)
绘制一个左上角为(x,y),高度为width以及height的矩形路径。
Path2D对象
在前面的例子中,使用一系列的路径或者内置的绘画命令来绘制图形。Path2D对象用来缓存或者记录绘画命令,以便快速的回顾路径。
Path2D()
Path2D()
会返回一个新的Path2D对象。
new Path2D() //空的Path对象
new Path2D(path) //克隆Path对象
new Path2D(d) //从SVG建立Path对象
Path2D 示例
function draw(){
let canvas = document.getElementById('canas')
if(canvas.getContext()){
let ctx = canvas.getContext()
//生成一个矩形的Path2D对象
let rectAngle = new Path2D()
rectAngle.rect(10,10,50,50)
//生成一个圆形的Path2D对象
let circle = new Path2D()
circle.arc(100,35,25,0,Math.PI*2,true)
//将对象画到画布上
ctx.stroke(rectAngle)
ctx.fill(circle)
}
}

代码中,先生成了一个渲染上下文的ctx
对象,然后又单独生成了一个矩形对象和一个圆形对象,最后使用将对象画在画布上。
使用 SVG paths
Path2D API 有一个强大的功能,可以使用SVG path data 来初始化canvas上的路径。
let p =new Path2D("M10 10 h 80 v 80 h -80 Z")
这条路径先移动到点(m10 10) 然后再水平移动80个单位(v 80) 然后下移80个单位(v 80) 接着左移80个单位(h -80),再回到起点(z)