前言
近日开始学习canvas绘图,学习的过程中就发现相关的API繁琐复杂,于是萌生了绘制解析图的想法,既然都是要绘图,那不如就直接用canvas来绘制。这篇博客就记述了我第一次绘制canvas图片的过程,虽然成品比较丑但这个过程是收获颇丰的(づ。◕ᴗᴗ◕。)づ。
一、car方法解析
1.car
方法介绍
ctx.car()
方法用于绘制一个弧线路径,它有如下的六个参数:
- x 圆心的x坐标
- y 圆心的y坐标
- radius 弧线的半径
- startAngle 弧线的起始角度(单位为弧度)
- endAngle 弧线的结束角度(单位为弧度)
- counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为顺时针)
2.弧线角度问题
(1)角度如何计算?
想要正确的使用car()
方法必须要搞清楚的一个问题就是:startAngle
和endAngle
这两个参数的角度是如何计算的?
这里的角度实际上是指从canvas坐标系的x轴正方向开始顺时针计算的角度。
而canvas坐标系是向左为x轴的正方向,向下为y轴的正方向。

因此在canvas坐标系中的 30°、130°、 230°、 330°,就如下图所示:

(2)counterclockwise参数与角度的关系
相关资料中对counterclockwise
参数的定义是:示是否逆时针计算起始角度和结束角度(默认为顺时针)。
我一开始以为意思是:
- 在如果
counterclockwise
为false
(默认情况),startAngle
和endAngle
这两个角度是从x轴正方向开始顺时针计算; - 如果
counterclockwise
为true
,则startAngle
和endAngle
从x轴正方向开始逆时针计算。

而实际上在我多次使用了arc()
方法后发现上面的这种理解其实是错误的 ,counterclockwise
参数的实际含义是:
- 在如果
counterclockwise
为false
(默认情况),则弧线是从开始角度沿顺时针方向到结束角度; - 如果
counterclockwise
为true
,则弧线是从开始角度沿逆时针方向到结束角度。

3.原理介绍
实际上car()
方法的原理就类似于绘制了一个圆,然后从这个圆上截取出一段弧线。
下面这张图就是我所绘制的"car方法解析图",在本文的剩余内容中我就将详细的介绍我绘制这张图的过程,以及绘制过程中所遇到的困难。

4.基于arc封装绘制圆弧的方法
(1)save与restore方法
在阅读一些文章的时候发现他们在封装canvas操作时都会用到ctx.save()
和ctx.restore()
这两个方法,其中ctx.save()
的作用是保存canvas的当前状态,ctx.restore()
的作用是将canvas恢复到最近的保存状态。因此利用这两个方法我们可以在某个绘制操作开始之前保存之前的canvas状态,在绘制操作结束之后在恢复之前的状态,从而将绘制操作时的canvas状态与之前的canvas状态进行"隔离"。
保存与恢复的canvas状态包括以下的内容:
- 当前的变换矩阵。
- 当前的剪切区域。
- 当前的虚线列表。
- 以下属性当前的值:
strokeStyle |
描边颜色/画笔颜色 |
---|---|
fillStyle |
填充颜色 |
globalAlpha |
透明度 |
lineWidth |
线宽 |
lineCap |
线段端点形状 |
lineJoin |
线段交点形状 |
lineDashOffset |
虚线偏移量 |
shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor |
阴影水平偏移距离、阴影垂直偏移距离、阴影模糊度、阴影颜色 |
globalCompositeOperation |
画布的显示模式 |
font |
字体样式(字体粗细、字体大小、字体种类) |
textAlign |
文本的对齐方式(水平对齐方式) |
textBaseline |
文本的基线属性(垂直对齐方式) |
direction |
文本的方向 |
imageSmoothingEnabled |
图片是否平滑 |
(2)完成封装
JavaScript
// 绘制弧线
/**
*
* @param {*} param0
* @param {*} param0.ctx canvas上下文对象
* @param {Array} param0.center 圆心坐标
* @param {number} param0.radius 圆的半径
* @param {number} param0.startAngle 弧线的开始角度
* @param {number} param0.endAngle 弧线的结束角度
* @param {boolean} param0.counterclockwise 弧线是否通过逆时针绘制
* @param {number} param0.width 弧线线宽
* @param {string} param0.color 弧线颜色
* @param {boolean} param0.isDash 弧线是否使用虚线
*/
function drawArc({
ctx,
center,
radius,
startAngle,
endAngle,
counterclockwise = false,
width = 5,
color = 'black',
isDash = false,
}) {
ctx.save()
ctx.lineWidth = width
ctx.strokeStyle = color
isDash ? ctx.setLineDash([10, 10]) : ctx.setLineDash([])
ctx.beginPath()
ctx.arc(
...center,
radius,
(startAngle * Math.PI) / 180,
(endAngle * Math.PI) / 180,
counterclockwise
)
ctx.stroke()
ctx.restore()
}
二、绘制虚线圆以及圆上的红色弧线
这一部分内容主要使用上一章介绍的 arc() 方法绘制。
1.绘制圆心点
首先绘制圆心点,但是canvas似乎没有绘制点的方法,因此只能用实心圆代替,于是我就封装了这样一个绘制点的方法:
JavaScript
// 绘制点
/**
*
* @param {*} param0
* @param {*} param0.ctx canvas上下文对象
* @param {Array} param0.position 点的位置坐标
* @param {number} param0.pixelSize 点的大小
* @param {string} param0.color 点的颜色
*/
function drawPoint({ ctx, position, pixelSize = 5, color = 'black' }) {
ctx.save()
ctx.setLineDash([])
ctx.beginPath()
ctx.arc(...position, pixelSize, 0, 2 * Math.PI, false)
ctx.fillStyle = color
ctx.fill()
ctx.restore()
}
利用这个drawPoint
方法我们就可以绘制一个红色的圆心点:
JavaScript
drawPoint({
ctx,
position:[900,500],
pixelSize:8,
color:'red'
})

2.绘制虚线圆
接下来就是绘制虚线圆,不过这时我就遇到了第一个难题:如何绘制虚线?
在查阅了一些资料后我选择使用ctx.setLineDash()
方法绘制虚线,这个方法在填充线时使用虚线模式,它接收一个数组,数组中的数字用于描述虚线中线段与间距的长度。

因此利用ctx.setLineDash()
和ctx.arc()
这两个方法就可以封装一个绘制圆的方法,同时这个方法支持绘制虚线圆:
JavaScript
// 绘制圆
/**
*
* @param {*} param0
* @param {*} param0.ctx canvas上下文对象
* @param {Array} param0.center 圆心坐标
* @param {number} param0.radius 圆的半径
* @param {number} param0.lineWidth 圆边线线宽
* @param {string} param0.lineColor 圆边线颜色
* @param {boolean} param0.isDash 圆边线是否使用虚线
* @param {string} param0.fillColor 圆的填充颜色 (默认透明)
*/
function drawCircle({
ctx,
center,
radius,
lineWidth = 5,
lineColor = 'black',
isDash = false,
fillColor = 'rgba(255,255,255,0)',
}) {
ctx.save()
ctx.lineWidth = lineWidth
ctx.strokeStyle = lineColor
isDash ? ctx.setLineDash([10, 10]) : ctx.setLineDash([])
ctx.fillStyle = fillColor
ctx.beginPath()
ctx.arc(...center, radius, (2 * Math.PI) / 180, 0)
ctx.stroke()
ctx.fill()
ctx.restore()
}
然后通过drawCircle()
方法就可以绘制虚线圆
JavaScript
// t-绘制虚线圆
drawCircle({
ctx,
center:[900,500],
radius:300,
isDash:true
})

3.绘制红色弧线
JavaScript
// t-绘制红色弧线
drawArc({
ctx,
center: [900, 500],
radius: 300,
startAngle: 240,
endAngle: 330,
color: 'red',
})

三、绘制坐标系x轴与角度相关的箭头线
这几个内容之所以放在一起那时因为它们共同都涉及到一个问题,那就是如何在canvs中绘制箭头?这也是本次绘图过程我所攻坚的一个重点问题。
1.如何绘制箭头线?
想要在在canvas中实现绘制带箭头的直线和弧线是比较复杂的,需要有一定的数学基础。我为此写了这几篇博客,大家可以参考一下:
绘制箭头的数学基础:
绘制箭头的具体实现:
2.进行绘制
JavaScript
// t-计算弧线点坐标
const pA = calcCoordinate([900, 500], 300, 240),
pB = calcCoordinate([900, 500], 300, 330)
// t-绘制从圆心点到弧线端点的箭头线
CanvasArrow.drawLineArrow({
ctx,
startPoint: [900, 500],
endPoint: pA,
})
CanvasArrow.drawLineArrow({
ctx,
startPoint: [900, 500],
endPoint: pB,
})

JavaScript
//t-绘制x轴
CanvasArrow.drawLineArrow({
ctx,
startPoint: [900, 500],
endPoint: [1400,500],
style:{
lineWidth:3
}
})
// t-绘制角度标识
CanvasArrow.drawArcArrow({
ctx,
center: [900, 500],
radius: 40,
startAngle: 0,
endAngle: 240,
})

四、添加文本
1.绘制文本的方法
canvas中提供了绘制文本的一系列属性和方法。
绘制文本的方法主要是fillText()
和strokeText()
,这两个方法都接收4个参数:要绘制的字符串、x坐标、y坐标和可选的最大像素宽度。它们的区别在于fillText()
使用fillStyle
绘制文本,而strokeText
则使用strokeStyle
来绘制文本。
canvas还提供了与文本相关的3个样式属性:
- font - 值为一个是一个字符串,字符串中分为三部分,分别用css语法来指定字体样式、大小、字体,例如:
"bold 16px Arial"
- textAlign - 指定文本的对齐方式,可能的值包括"start"、"end"、"left"、"right"和"center"。
- textBaseLine - 指定文本的基线,可能的值包括"top"、"hanging"、"middle"、"alphabetic"、"ideographic"和"bottom"。
我基于上面提到的这些原生属性和方法进行封装,得到了两个添加文本的方法。
JavaScript
//添加文本
/**
*
* @param {*} ctx 2d绘图上下文
* @param {string} text 文本内容
* @param {*} position 文本添加的位置
* @param {*} fontWeight 字体的粗细程度
* @param {*} fontSize 字体大小
* @param {*} fontFamily 字体种类
* @param {*} fontColor 文本颜色
* @param {*} textAlign 文本水平对齐方式
* @param {*} textBaseLine 文本基线位置
*/
function addText({
ctx,
text,
position,
fontWeight = 'normal',
fontSize = '18px',
fontFamily = 'Arial',
fontColor = 'black',
textAlign = 'center',
textBaseLine = 'middle',
}) {
ctx.save()
ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`
ctx.fillStyle = fontColor
ctx.textAlign = textAlign
ctx.textBaseLine = textBaseLine
ctx.fillText(text, ...position)
}
// 批量添加文字
/**
*
* @param {*} ctx canvas上下文
* @param {Array} texts 文本内容数组 , 数组中的元素为文本内容对象,
每个文本内容对象都包含两个属性text和position,分别表示文本内容和文本位置
* @param {Object} textStyle 文本样式对象 , 对象内容参考addText中与样式相关的参数
* @example
* */
function addTexts(ctx, texts, textStyle = {}) {
texts.forEach((textObj) => {
addText({
ctx,
...textObj,
...textStyle,
})
})
}
2.进行文本绘制
JavaScript
addTexts(ctx, [
{
text: 'x轴',
position: [1380, 530],
},
{
text: '开始角度',
position:[770,380]
},
{
text:'结束角度',
position:[1100,440]
}
])
addText({
ctx,
text: '绘制出来的弧线',
position: [940, 180],
fontWeight:'bold',
fontSize:'24px'
})

至此我们的car方法解析图就绘制完成了,虽然感觉有点朴素有点丑,但是重要的是在绘制的过程中我学到了许多知识,不是吗!
五、下载绘制的canvas图片
下载canvas图片主要是分为两步,第一步是将我们绘制的canvas转成一个URL;然后第二步是下载这个URl。
1.canvas转URL
主要是使用canvas元素节点的toDataURL()
方法。这个方法返回一个包含canvas图片展示的 data URL。我们可以通过type
参数来指定图片的类型(type
的属性值为MIME类型的字符串,默认是image/png
即PNG格式)
因此canvas转URL的代码就如下所示:
JavaScript
const drawing = document.getElementById('drawing')
const imgUrl = drawing.toDataURL()
2.根据URL下载文件
这部分内容我就不详细论述了,具体方法可以参考我写的这篇文章:
前端文件下载和文件读取方法研究_只有文件路径前端怎么读取文件-CSDN博客
参考资料
- 《JavaScript高级程序设计》第18章 动画与canvas图形
- 学习 HTML5 Canvas 这一篇文章就够了 | 菜鸟教程
- CanvasRenderingContext2D.save() - Web API 接口参考 | MDN
- canvas如何绘制虚线_canvas 虚线-CSDN博客
- canvas绘制虚线setLineDash-CSDN博客
- CanvasRenderingContext2D.setLineDash() - Web API 接口参考 | MDN
- 【Java AWT 图形界面编程】在 Canvas 画布中绘制箭头图形 ( 数据准备 | 几个关键的计算公式 | 绘制箭头直线和尾翼 )-腾讯云开发者社区-腾讯云
- Canvas学习:绘制箭头_canvas画箭头-CSDN博客
- Arrows with Canvas
- HTMLCanvasElement.toDataURL() - Web API 接口参考 | MDN
- 【canvas】导出图片背景色_canvas 导出图片,背景色设置-CSDN博客