前端开发系列026-基础篇之Canvas绘图(曲线)
一、Canvas中的弧度、曲线和圆弧
夹角
从一个点发射(延伸)出两条线段,两条线相交的部分会构成一个夹角。
角度
两条相交直线中的任何一条与另一条相叠合时必须转动的量的量度,单位符号为°
。
周角
一条直线围绕起点需要与自己相叠合时必须转动的量的量度被称为周角,周角等分为360
度。
弧度
角度量单位,弧长等于半径的弧所对圆心角为1弧度(弧长等于半径时射线夹角为1弧度)。
在使用JavaScript编写代码进行相关计算的时候,经常需要使用Math提供的成员,这里简单说明。
Math.PI 代表着π。
Math.sin(弧度) 夹角对面的边 与 斜边的比值。
Math.cos(弧度) 夹角侧面的边 与 斜边的比值。
这里给出圆形上点坐标的计算公式,其中x0
和y0
为圆心坐标,rad
为弧度,R
为圆的半径。
坐标 ( x0 + Math.cos(rad) x R , y0 + Math.sin(rad) x R )
绘制圆弧
语法
ctx.arc(x,y,r,startAngle,endAngle,counterclockwise);
作用
通过该方法来绘制圆弧或者(半)圆。
参数
- x 圆心X轴坐标
- y 圆心Y轴坐标
- r 圆的半径
- startAngle 开始弧度
- endAngle 结束弧度
- counterclockwise 是否逆时针旋转(默认为false)
绘制圆弧曲线
语法
ctx.arcTo(x1,y1,x2,y2,r);
作用
参考两个点并根据指定半径来创建一条圆弧路径。
备注
绘制的圆弧与当前点到第一个点的连线相切且与第一第二个点的连线也相切。
说明
arcTo
方法的这些特性决定了该方法非常适合用来绘制圆角矩形。
参数
- x1 第一个参考点的X轴坐标
- y1 第一个参考点的Y轴坐标
- x2 第二个参考点的X轴坐标
- y3 第二个参考点的Y轴坐标
- r 圆的半径
圆形渐变
语法
ctx.createRadialGradient(x0,y0,r0,x1,y1,r1);
作用
通过该方法来绘制圆弧或者(半)圆。
参数
- x0 渐变开始圆的X轴坐标
- y0 渐变开始圆的Y轴坐标
- r0 开始圆的半径
- x1 渐变结束圆的X轴坐标
- y1 渐变结束圆的Y轴坐标
- r1 结束圆的半径
二、Canvas曲线-圆弧绘制示例
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//001 通过代数方程来绘制直线
//设置路径(起点)
ctx.moveTo(0,0);
for(var x = 30,y = 0; x < 1000 ; x++)
{
//通过代数方程来绘制直线
y = x / 2 * 0.3;
ctx.lineTo(x,y);
}
//设置描边的颜色样式
ctx.strokeStyle = "#0Af";
//描边绘制出图案
ctx.stroke();
//002 通过三角函数来绘制曲线(正玄/余弦)
ctx.beginPath();
for(var x = 30,y = 0; x < 1000 ; x++)
{
// 高度 * 波长 + 中心轴位置
y = 50 * Math.sin(x/25) + 100;
ctx.lineTo(x,y);
}
//设置描边的颜色样式
ctx.strokeStyle = "red";
//描边绘制出图案
ctx.stroke();
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
function draw(offsetX,n){
var dig = Math.PI / 15 * n;
ctx.beginPath();
for(var i = 0 ; i < 30 ; i++)
{
var x = Math.sin(i * dig);
var y = Math.cos(i * dig);
ctx.lineTo(offsetX + x * 80,150 + y * 80);
}
//闭合路径
ctx.closePath();
//设置样式并填充
ctx.fillStyle = "#fff";
ctx.fill();
//设置样式并描边
ctx.strokeStyle = "#666";
ctx.stroke();
}
var data = [14,13,19,7,26];
data.forEach(function(n,i){
draw((i + 1) * 160,n);
})
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var x0 = 100,y0 = 100,
x1 = 500,y1 = 100,
x2 = 450,y2 = 150;
ctx.beginPath();
ctx.moveTo(x0,y0);
ctx.arcTo(x1,y1,x2,y2,30);
ctx.strokeStyle = "red";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x0,y0);
ctx.lineTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.fillText('x0,y0',x0,y0+10)
ctx.fillText('x1,y1',x1+10,y1+10)
ctx.fillText('x2,y2',x2+10,y2)
ctx.strokeStyle = "#333";
ctx.stroke();
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
function drawRoundedRect(x,y,w,h,r,isFill,isStrokeRect){
ctx.beginPath();
ctx.moveTo( x + r , y );
ctx.arcTo( x + w , y , x + w , y + h , r);
ctx.arcTo( x + w , y + h , x , y + h , r);
ctx.arcTo( x , y + h , x , y , r);
ctx.arcTo( x , y , x + r , y , r);
if(isFill) {
ctx.fillStyle = getRandomColor();
ctx.fill();
}else
{
ctx.strokeStyle = getRandomColor();
ctx.stroke();
}
if(isStrokeRect)
{
ctx.beginPath();
ctx.moveTo( x + r , y );
ctx.lineTo(x + w , y );
ctx.lineTo(x + w , y + h);
ctx.lineTo(x , y + h);
ctx.lineTo(x , y);
ctx.lineTo(x + r , y);
ctx.fillStyle = "#000";
ctx.fillText("x0,y0",x + r,y);
ctx.fillText("x1,y1",x + w,y);
ctx.fillText("x2,y2",x + w,y + h + 10);
ctx.fillText("x3,y3",x-10,y + h + 10);
ctx.fillText("x4,y4",x - 10,y-10);
ctx.fillText("x5,y5",x + r,y + 10);
ctx.stroke();
}
}
drawRoundedRect(50,40,100,100,50,false,true);
drawRoundedRect(200,40,100,100,50,true,false);
drawRoundedRect(350,40,100,100,10,true);
drawRoundedRect(500,40,100,100,20);
drawRoundedRect(650,40,120,100,30,true);
function getRandomColor(){
var r = getRandom();
var g = getRandom();
var b = getRandom();
return "rgb("+r+","+g+","+b+")";
}
function getRandom(){
return Math.floor(Math.random() * 256);
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//绘制弧线
ctx.arc(100,100,50,3/2 * Math.PI,2 * Math.PI,false);
ctx.stroke();
//绘制扇形
ctx.beginPath();
ctx.moveTo(200,100);
ctx.arc(200,100,50,3/2 * Math.PI,2 * Math.PI,false);
ctx.lineTo(200,100);
ctx.strokeStyle = "#f09";
ctx.stroke();
//填充扇形
ctx.beginPath();
ctx.moveTo(300,100);
ctx.arc(300,100,50,3.2/2 * Math.PI,3.8/2 * Math.PI,false);
ctx.lineTo(300,100);
ctx.fillStyle = "#195";
ctx.fill();
//绘制半圆
ctx.beginPath();
ctx.strokeStyle = "#666";
ctx.arc(450,100,50,Math.PI,2 * Math.PI,false);
ctx.closePath();
ctx.stroke();
//绘制圆形
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.arc(570,80,40,0,2 * Math.PI,false);
ctx.stroke();
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var x = 100;
var y = 100;
var r = 50;
for(var i = 0 ; i < 5 ; i++)
{ ctx.beginPath();
if( i >=3)
{
ctx.arc(x + (i * 80) -200,y + 60,r,0,2*Math.PI,false);
}else{
ctx.arc(x + (i * 80),y,r,0,2*Math.PI,false);
}
ctx.strokeStyle = getRandomColor();
ctx.stroke();
}
function getRandomColor(){
var r = getRandom();
var g = getRandom();
var b = getRandom();
return "rgb("+r+","+g+","+b+")";
}
function getRandom(){
return Math.floor(Math.random() * 256);
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//描边
drawCircle(100,100,40,2,true);
drawCircle(200,100,40,3,true);
drawCircle(300,100,40,4,true);
drawCircle(400,100,40,20,true);
drawCircle(500,100,40,100,true);
drawCircle(600,100,40,200,true);
//填充
drawCircle(100,200,40,2);
drawCircle(200,200,40,3);
drawCircle(300,200,40,4);
drawCircle(400,200,40,20);
drawCircle(500,200,40,100);
drawCircle(600,200,40,200);
function drawCircle(x,y,r,n,isStroke){
for(var i = 0 ; i < n ; i++)
{
//计算开始和结束的角度
var angle = 2 * Math.PI / n;
var startAngle = angle * i;
var endAngle = angle * (i + 1);
//开始路径
ctx.beginPath();
//设置绘制圆的起点
ctx.moveTo(x,y);
ctx.arc(x,y,r,startAngle,endAngle,false);
if(isStroke)
{
// ctx.strokeStyle = getRandomColor();
ctx.stroke();
}else
{
ctx.fillStyle = getRandomColor();
ctx.fill();
}
}
}
//获取填充的颜色/随机
function getRandomColor(){
var r = getRandom();
var g = getRandom();
var b = getRandom();
return "rgb("+r+","+g+","+b+")";
}
function getRandom(){
return Math.floor(Math.random() * 256);
}
function PieChart (ctx){
this.ctx = ctx || document.getElementById("canvas").getContext("2d");
this.x = this.ctx.canvas.width/2 - 30;
this.y = this.ctx.canvas.height/2;
this.r = 120;
this.outLine = 20;
this.dataList = null;
}
PieChart.prototype = {
constructor:PieChart,
init:function(dataList){
this.dataList = dataList || [{title:"默认",value:100}];
//根据数据来计算并转换弧度
this.transformAngle();
//绘制饼状图
this.drawPie();
},
drawPie:function(){
var startAngle = 0,endAngle;
for(var i = 0 ; i < this.dataList.length ; i++)
{
var item = this.dataList[i];
endAngle = startAngle + item.angle;
this.ctx.beginPath();
this.ctx.moveTo(this.x,this.y);
this.ctx.arc(this.x,this.y,this.r,startAngle,endAngle,false);
var color= this.ctx.strokeStyle= this.ctx.fillStyle= this.getRandomColor();
this.ctx.stroke();
this.ctx.fill();
//绘制标题
this.drawPieTitle(startAngle,item.angle,color,item.title)
//绘制图例
this.drawPieLegend(i,item.title);
startAngle = endAngle;
}
},
drawPieTitle:function(startAngle,angle,color,title){
var edge = this.r + this.outLine;
var edgeX = Math.cos(startAngle + angle / 2) * edge; /*x轴方向的直角边*/
var edgeY = Math.sin(startAngle + angle / 2) * edge; /*y轴方向的直角边*/
var outX = this.x + edgeX; /*计算延伸出去的点坐标*/
var outY = this.y + edgeY;
//画出坐标点
this.ctx.beginPath();
this.ctx.moveTo(this.x,this.y);
this.ctx.lineTo(outX,outY);
this.ctx.strokeStyle = color;
this.ctx.stroke();
//绘制文字下划线
var textWidth = this.ctx.measureText(title).width + 5;
var lineX = outX > this.x ? outX + textWidth : outX - textWidth;
this.ctx.lineTo(lineX,outY);
this.ctx.stroke();
//绘制文字
this.ctx.font = "15px KaiTi";
this.ctx.textAlign = outX > this.x ? "left" : "right";
this.ctx.textBaseline = "bottom";
this.ctx.fillText(title,outX,outY);
},
drawPieLegend:function(index,title){
//在计算的时候最好的能够反着计算
var space = 10;
var rectW = 40;
var rectH = 20;
var rectX = this.x + this.r + 80;
var rectY = this.y + (index * 30);
//绘制矩形
this.ctx.fillRect(rectX,rectY,rectW,rectH);
// this.ctx.beginPath();
// 绘制文字
this.ctx.textAlign = 'left';
this.ctx.textBaseline = 'top';
this.ctx.fillStyle = "#000";
this.ctx.fillText(title,rectX + rectW + space,rectY);
},
getRandomColor:function(){
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
return 'rgb('+r+','+g+','+b+')';
},
transformAngle:function(){
var self = this;
var total = 0;
this.dataList.forEach(function(item,i){
total += item.value;
})
this.dataList.forEach(function(item,i){
self.dataList[i].angle = 2 * Math.PI * item.value/total;
})
},
}
var data = [{value:20,title:"UI"},{value:26,title:"java"},
{value:20,title:"iOS"},{value:63,title:"H5"},{value:25,title:"Node"}]
var pie = new PieChart().init(data);
三、Canvas中的贝塞尔曲线
贝塞尔曲线(Bézier curve
),最初由法国物理学家和数学家Paul de Casteljau
发明,1962年被法国工程师皮埃尔·贝塞尔(Pierre Bézier
)广泛发表并运用在汽车的车身设计上,现在多应用在计算机图形系统中。
贝塞尔曲线分为平方(quadratic
)贝塞尔曲线和立方(cubic
)贝塞尔曲线两其中平方贝塞尔曲线是一种二次曲线,由两个锚点和一个控制点总共三个点来定义,而立方贝塞尔曲线是一种三次曲线,由两个锚点和两个控制点共四个点来定义。它们的区别在于立方贝塞尔曲线能够在两个方向上弯曲。
Canvas支持两种贝塞尔曲线,分别由quadraticCurveTo
和bezierCurveTo
方法来实现。
二次贝塞尔曲线
语法
ctx.quadraticCurveTo(x0,y0,x1,y1);;
作用
通过使用表示二次贝塞尔曲线的指定控制点,向当前路径添加一个点绘制曲线。
参数
- x0 控制点的X轴坐标
- y0 控制点的Y轴坐标
- x1 结束点(锚点)的X轴坐标
- y1 结束点(锚点)的Y轴坐标
说明
二次贝塞尔曲线需要两个点。分别是用于二次贝塞尔计算中的控制点和曲线的结束点。
注意
曲线还需要一个开始点(路径最后的点)如果路径不存在,那么可以使用moveTo()
方法来定义。
三次贝塞尔曲线
语法
ctx.bezierCurveTo(x0,y0,x1,y1,x2,y2);;
作用
通过使用表示二次贝塞尔曲线的指定控制点,向当前路径添加一个点绘制曲线。
参数
- x0 第一个控制点的X轴坐标
- y0 第一个控制点的Y轴坐标
- x1 第二个控制点的X轴坐标
- y1 第二个控制点的Y轴坐标
- x2 结束点(锚点)的X轴坐标
- y2 结束点(锚点)的Y轴坐标
说明
三次贝塞尔曲线需要三个点,两个控制点和一个锚点。
注意
曲线还需要一个开始点(路径最后的点)如果路径不存在,那么可以使用moveTo()
方法来定义。
四、Canvas贝塞尔曲线绘制示例
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//设置曲线的起点(当前路径的最后点没有则通过moveTo设置)
ctx.moveTo(100,100);
ctx.quadraticCurveTo(100,300,500,200);
ctx.stroke();
//绘制文字
var margin = 15;
ctx.fillText("(100,100)",100 - margin,100 - margin);
ctx.fillText("(100,300)",100 - margin,300 + margin);
ctx.fillText("(500,200)",500 - margin,200 + margin);
//绘制线条
ctx.beginPath();
ctx.moveTo(100,100);
ctx.lineTo(100,300);
ctx.lineTo(500,200);
ctx.strokeStyle = "red";
ctx.stroke();
//绘制点
ctx.beginPath();
ctx.arc(100,100,3,0,2 * Math.PI);
ctx.arc(100,300,3,0,2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(500,200,3,0,2 * Math.PI);
ctx.fill();
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//设置曲线的起点(当前路径的最后点没有则通过moveTo设置)
ctx.moveTo(100,100);
ctx.bezierCurveTo(100,300,300,50,500,200);
ctx.stroke();
//绘制文字
var margin = 15;
ctx.fillText("起点 (100,100)",100 - margin,100 - margin);
ctx.fillText("控制点 (100,300)",100 - margin,300 + margin);
ctx.fillText("(300,50)",300 - margin,50 - margin);
ctx.fillText("(500,200)",500 - margin,200 + margin);
//绘制线条
ctx.beginPath();
ctx.moveTo(100,100);
ctx.lineTo(100,300);
ctx.lineTo(300,50);
ctx.lineTo(500,200);
ctx.strokeStyle = "red";
ctx.stroke();
//绘制点
ctx.beginPath();
ctx.arc(100,100,3,0,2 * Math.PI);
ctx.arc(100,300,3,0,2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(300,50,3,0,2 * Math.PI);
ctx.arc(500,200,3,0,2 * Math.PI);
ctx.fill();
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//绘制贝塞尔曲线
function drawBezierCurve(dx,dy,n){
var s = 120;
var x0,x1,x3,y1,y2,y3;
var dig = Math.PI / 15 * n;
ctx.beginPath();
for(var i = 0 ; i < 30 ; i++)
{
var X = Math.sin(i * dig);
var Y = Math.cos(i * dig);
x0 = dx + X * s;
x1 = dx + X * s + 100;
x2 = dx + X * s;
y0 = dy + Y * s - 100;
y1 = dy + Y * s;
y2 = dy + Y * s;
ctx.bezierCurveTo(x0 , y0 , x1 , y1 , x2 , y2);
}
ctx.closePath();
//绘制和填充
ctx.fillStyle = "#eee";
ctx.strokeStyle = "red";
ctx.fill();
ctx.stroke();
}
drawBezierCurve(150,250,13);
drawBezierCurve(480,250,24);
drawBezierCurve(820,250,31);