直线图形和曲线图形是Canvas中的两大基本图形,《好玩的Canvas:直线(线段)、图形的绘制》已经介绍了直线图形的绘制,本文将讲述如何在Canvas中绘制曲线图形。
初中数学里面的曲线定义如下:
任何一根连续的线条都称为曲线,包括直线、折线、线段、圆弧等。
弧线是圆周的一部分,且每个点曲率相同,曲线包含弧线。本文讲述的曲线主要是常规的曲线和特殊的曲线——弧线,包含圆形绘制、弧线绘制、贝塞尔曲线绘制。
Canvas圆形的绘制
描边圆绘制
在Canvas中,使用arc()方法来绘制圆形,代码如下
cxt.beginPath();
cxt.arc(x,y,radius,startAngle,endAngle,anticlockwise);
cxt.closePath();
解释:
- x和y是圆心坐标,也就是图中O(x0,y0);
- radius是圆的半径;
- startAngle和endAngle是图中起始角和结束角,单位为弧度;
- anticlockwise指绘制的方向,是一个布尔值,true代表逆时针方向绘制,false代表顺时针方向绘制,默认是false,顺时针方向绘制;
注意:
需要先调用beginPath()
方法来开始一个新路径,然后才可以开始绘制,绘制完成之后,还需要调用closePath()
方法来关闭当前路径。
下面是一段代码示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>圆形的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id){
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
cxt.beginPath();
cxt.arc(150,150,100,0*Math.PI/180,360*Math.PI/180,false);
cxt.closePath();
cxt.stroke();
</script>
</html>
效果如下:
上面这个示例画的圆就采用stroke()
方法进行描边了,我们还可以给边设置属性,代码如下:
cxt.strokeStyle = "颜色值";
下面是一个代码示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>圆形的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id){
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
cxt.beginPath();
cxt.arc(150,150,100,0*Math.PI/180,360*Math.PI/180,false);
cxt.closePath();
cxt.stroke();
cxt.beginPath();
cxt.arc(450,150,100,0*Math.PI/180,360*Math.PI/180,false);
cxt.closePath();
cxt.strokeStyle="green";
cxt.stroke();
//1/4圆 逆时针方向绘制
cxt.beginPath();
cxt.arc(150,350,100,0*Math.PI/180,45*Math.PI/180,false);
cxt.closePath();
cxt.strokeStyle="green";
cxt.stroke();
//1/4圆顺时针方向绘制变成 3/4圆
cxt.beginPath();
cxt.arc(450,350,100,0*Math.PI/180,45*Math.PI/180,true);
cxt.closePath();
cxt.strokeStyle="green";
cxt.stroke();
</script>
</html>
效果如下:
填充圆绘制
在Canvas中,采用fill()方法来绘制填充圆。
cxt.beginPath();
cxt.arc(x,y,radius,startAngle,endAngle,anticlockwise);
cxt.closePath();
cxt.fillStyle = "颜色值";
cxt.fill();
下面是一个代码示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>圆形的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id){
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
//描边圆
cxt.beginPath();
cxt.arc(150,150,100,0*Math.PI/180,360*Math.PI/180,false);
cxt.closePath();
cxt.stroke();
//绿色边描边圆
cxt.beginPath();
cxt.arc(250,150,100,0*Math.PI/180,360*Math.PI/180,false);
cxt.closePath();
cxt.strokeStyle="green";
cxt.stroke();
//红色填充圆
cxt.beginPath();
cxt.arc(350,150,100,0*Math.PI/180,360*Math.PI/180,false);
cxt.closePath();
cxt.fillStyle="red";
cxt.fill();
//绿色填充圆
cxt.beginPath();
cxt.arc(450,150,100,0*Math.PI/180,360*Math.PI/180,false);
cxt.closePath();
cxt.fillStyle="green";
cxt.fill();
//蓝色描边 绿色填充圆(先填充后描边,不然样式会被覆盖)
cxt.beginPath();
cxt.arc(550,150,100,0*Math.PI/180,360*Math.PI/180,false);
cxt.closePath();
cxt.fillStyle="green";
cxt.fill();
cxt.strokeStyle = "blue";
cxt.stroke();
//1/4圆 逆时针方向绘制
cxt.beginPath();
cxt.arc(150,350,100,0*Math.PI/180,45*Math.PI/180,false);
cxt.closePath();
cxt.fillStyle="blue";
cxt.fill();
</script>
</html>
效果如下:
注意:
后面的操作呈现的样式会覆盖前面的操作呈现的样式。
弧线的绘制
arc()绘制弧线
前面我们采用arc()绘制了圆形和1/4圆,代码最后一行有closePath()
,将这一行代码去掉就变成了圆弧,因为圆弧不是闭合路径,closePath()
方法的作用是关闭路径、连接起点与终点。一个简单示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>弧线的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id){
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
//1/4圆 逆时针方向绘制
cxt.beginPath();
cxt.arc(150,350,100,0*Math.PI/180,45*Math.PI/180,false);
// cxt.closePath();
cxt.stroke();
</script>
</html>
效果如下:
stroke()
和fill()
只能绘制当前路径,不能同时绘制几个路径,下面是一个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>弧线的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id) {
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
//情况1:
// 先绘制直线
cxt.moveTo(250, 50);
cxt.lineTo(250, 350);
// cxt.stroke();//画完直线不使用stroke()
//再逆时针方向绘制 1/4圆
cxt.beginPath();
cxt.arc(150, 350, 100, 0 * Math.PI / 180, 45 * Math.PI / 180, false);
cxt.stroke();
//情况2:
// 先绘制绘制直线
cxt.moveTo(300, 50);
cxt.lineTo(300, 350);
cxt.stroke(); //画完直线使用stroke()
//再逆时针方向绘制 1/4圆
cxt.beginPath();
cxt.arc(200, 350, 100, 0 * Math.PI / 180, 45 * Math.PI / 180, false);
cxt.stroke();
</script>
</html>
效果如下:
可以看出stroke()
和fill()
只能绘制当前路径,不能同时绘制几个路径。
arcTo()方法绘制弧线
除了使用arc()
方法可以绘制弧线外,我们还可以使用arcTo()
方法绘制弧线。
cxt.arcTo(cx,cy,x2,y2,radius);
解释:
- cx,cy是图中控制点坐标(cx,cy);
- x2,y2是图中结束点坐标(x2,y2);
- radius是图中圆O的半径;
开始点、控制点、结束点这三个点组成两条如图中直线,两条直线夹一个角,在这个角中做与两条直线相切的圆,所夹弧即为绘制的圆弧。此时会有一个问题,圆的半径不同,那么两个切点位置也不同,所以切点不一定就是开始点和控制点,也就是说切点在两条线段延长线上。
细心的朋友还会发现上面语法中没有开始点的坐标,那么开始点由谁来设置呢?
答案很简单,一般由moveTo()
或lineTo()
提供开始点,下面是一个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>弧线的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id) {
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
//情况1:切点正好与开始点和结束点重合
cxt.moveTo(20,40);
cxt.arcTo(40,40,40,60,20);
cxt.stroke();
//情况2:切点与开始点和结束点不重合
cxt.moveTo(20,40);
cxt.arcTo(90,40,90,60,20);
cxt.stroke();
//情况3:直线弧线结合做出情况2效果
cxt.moveTo(120,40);
cxt.lineTo(140,40);
cxt.arcTo(160,40,160,60,20);
cxt.stroke();
</script>
</html>
效果如下:
从情况2和情况3对比可以看出如果切点和开始点不重合时,arcTo()
方法还将添加一条当前切点到开始点的直线线段。
圆角按钮的绘制
上面是一个示例,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>圆角按钮的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id) {
return document.getElementById(id);
}
function create_button(cxt,x,y,w,h,r,fillStyle=""){
cxt.beginPath();
cxt.moveTo(x+r,y);
cxt.lineTo(x+w-r,y);
cxt.arcTo(x+w,y,x+w,y+r,r);
cxt.lineTo(x+w,y+h-r);
cxt.arcTo(x+w,y+h,x+w-r,y+h,r);
cxt.lineTo(x+r,y+h);
cxt.arcTo(x,y+h,x,y+h-r,r);
cxt.lineTo(x,y+r);
cxt.arcTo(x,y,x+r,y,r);
if(fillStyle){
cxt.fillStyle = fillStyle;
cxt.fill();
}else{
cxt.stroke();
}
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
create_button(cxt,5,5,100,50,10);
create_button(cxt,120,5,100,50,10,"red");
</script>
</html>
效果如下:
绘制贝塞尔曲线
前面已经介绍如何使用arc()
和arcTo()
绘制圆形和圆弧,接下来介绍一下如何使用Canvas绘制曲线,说到曲线,做前端开发的都应该听说的贝塞尔曲线的大名,这里也只介绍一下贝塞尔曲线的用法。
贝塞尔曲线简介
贝塞尔曲线又叫贝兹曲线、贝济埃曲线,是计算机图形学中非常重要的曲线,前端开发中可应用于轨迹的绘制以及定义动画曲线。
贝塞尔曲线原理
贝塞尔曲线是由n个点来决定,其曲线轨迹公式为:
n代表贝塞尔曲线是几阶曲线。
详细原理可以参考下面给出的链接:
https://baike.baidu.com/item/%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF/1091769?fr=aladdin
https://segmentfault.com/a/1190000018502700#item-1
二阶贝塞尔曲线绘制
和前面一样,二阶贝塞尔曲线也需要开始点、控制点、结束点,但是不需要半径,具体如何绘制出来的可以参考上面发的链接,还可以看看photoshop的钢笔工具。
cxt.quadraticCurveTo(cx,cy,x2,y2);
解释:
- cx,cy是控制点的坐标;
- x2,y2是结束点坐标;
- 起始点坐标由
moveTo()
或lineTo()
确定;
下面是一段代码示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>二阶贝塞尔曲线的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id) {
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
cxt.moveTo(50,50);
cxt.quadraticCurveTo(50,100,100,50);
cxt.stroke();
</script>
</html>
效果如下:
手工计算然后再采用贝塞尔曲线绘制我们想要的图形有点小难,我们可以用下面工具辅助设计:
演示:http://wx.karlew.com/canvas/bezier/
源码:https://github.com/karlew/bezier-tool-for-canvas
下面是一个用贝塞尔曲线绘制云朵的案例,首先打开演示地址,点击upload上传效果图图片,根据图片线条,手动匹配出相应的贝塞尔曲线。
将上面这张云朵图片上传,然后手动调整贝塞尔曲线匹配轮廓,这个工具的一些使用技巧就不在此处阐述。
完整代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>二阶贝塞尔曲线的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id) {
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
// cxt.lineWidth = 10;//设置线条宽度
cxt.beginPath();
cxt.moveTo(79, 143);
cxt.quadraticCurveTo(90, 18, 214, 84);
cxt.moveTo(352, 91);
cxt.quadraticCurveTo(284, 18, 214, 84);
cxt.moveTo(352, 91);
cxt.quadraticCurveTo(541, 115, 399, 204);
cxt.moveTo(283, 239);
cxt.quadraticCurveTo(359, 292, 399, 204);
cxt.moveTo(283, 239);
cxt.quadraticCurveTo(222, 285, 158, 243);
cxt.moveTo(79, 143); //与前面形成闭合
cxt.quadraticCurveTo(13, 243, 158, 243);
cxt.stroke();
</script>
</html>
效果如下:
三阶贝塞尔曲线
cxt.bezierCurveTo(cx1,cy1,cx2,cy2,x,y);
三阶贝塞尔曲线有两个控制点,下面是一个简单demo:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>三阶贝塞尔曲线的绘制</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id) {
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
cxt.beginPath();
cxt.moveTo(30,30);
cxt.bezierCurveTo(40,240,840,500,280,365);
cxt.stroke();
</script>
</html>
效果如下:
下面还是使用前面的辅助设计工具,绘制一个爱心,爱心素材如下:
在工具中去获取坐标点:
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>三阶贝塞尔曲线绘制爱心</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
</body>
<script>
function get_dom(id) {
return document.getElementById(id);
}
var canvas = get_dom('canvas');
var cxt = canvas.getContext("2d");
cxt.beginPath();
cxt.moveTo(245, 383);
cxt.bezierCurveTo(610, 144, 412, -7, 250, 84);
cxt.moveTo(245, 383);
cxt.bezierCurveTo(-3, 206, 11, 3, 250, 84);
cxt.fillStyle = "red";
cxt.fill();
</script>
</html>
效果如下:
总结
理论上可以利用Canvas的曲线绘制各种图形,也可以利用曲线绘制一些图表。