利用canvas画一片星空
效果图如下:
观察这篇星空:
多一半的位置被随机分布的星星所覆盖,右上角有一轮月牙,背景色为深蓝色到黑色的渐变色,下方是一片波浪形绿地,绿地颜色也为渐变色。
1.首先,如何绘制渐变色?
已知可以绘制的渐变色有两种情况,径向渐变(Radial Gradient)和线性渐变(Linear Gradient)。
径向渐变与线性渐变效果如图:
第一张图为线性渐变,第二张图为径向渐变
·线性渐变Linear Gradient
eg:var grd = cxt.createLinearGradient(xstart,ystart,xend,yend);
//构成渐变线,得到渐变线的方向和尺度
grd.addColorStop(stop,color);
stop:介于 0.0 与 1.0 之间的值,表示渐变中开始与结束之间的位置
color 在结束位置显示的 CSS 颜色值
上图中线性渐变代码如下:
var linearGrad = context1.createLinearGradient(0,0,800,800);
linearGrad.addColorStop(0.0, '#fff');
linearGrad.addColorStop(0.3, 'yellow');
linearGrad.addColorStop(0.6, '#e89abe');
linearGrad.addColorStop(1.0, 'blue');
context1.fillStyle = linearGrad;
context1.fillRect(0,0,800,800);
在0.0-1.0种用了4种颜色,这4种颜色 呈线性渐变
·径向渐变Radial Gradient
呈放射状渐变,定义在两个同心圆,而非两点
eg:var grd = cxt.createRadialGradient(x0,y0,r0,x1,y1,r1);
grd.addColorStop(stop,color);
上图中径向渐变代码如下:
var radialGrad = context2.createRadialGradient(400,400,0,400,400,500);
radialGrad.addColorStop(0.0, '#fff');
radialGrad.addColorStop(0.3, 'yellow');
radialGrad.addColorStop(0.6, '#e89abe');
radialGrad.addColorStop(1.0, 'blue');
context2.fillStyle = radialGrad;
context2.fillRect(0,0,800,800);
在0.0-1.0种用了4种颜色,这4种颜色 呈径向渐变
而在星空例子中径向渐变效果更好;
径向渐变呈放射状渐变,是定义在两个同心圆之间的
通过createRadialGradient(x0,y0,r0,x1,y1,r1)函数,其中,x0,y0,r0表示小圆
原点坐标以及半径,,x1,y1,r1表示大圆原点坐标及半径;
再利用addColorStop(stop,color)函数,在0.0处定义深蓝色,在1.0初定义黑色
这样就可以做出放射状的渐变色了;
2.如何绘制漫天繁星?
首先看单独的一颗五角星如何绘制:
(图片转自慕课网liuyubobobo讲师的课程Canvas绘图详解)
根据观察,可以得到,五角星的绘制其实是基于两个同心圆的基础上的,根据这两个同心圆,可以算出每个角的坐标
代码如下:
function starPath(cxt){//绘制一个标准的五角星
cxt.beginPath();
for (var i = 0; i < 5; i++) {
cxt.lineTo(Math.cos( (18+i*72)/180*Math.PI),
-Math.sin((18+i*72)/180*Math.PI));
cxt.lineTo(Math.cos( (54+i*72)/180*Math.PI)*0.5,
-Math.sin((54+i*72)/180*Math.PI)*0.5);
}
cxt.closePath();
}
这是以大圆半径为1 来绘制的一个标准的五角星;(我们令小圆半径为大圆半径的0.5倍)
其中一个注意点:在Math中的三角函数是以弧度值来计算的,因此在使用sin与cos时,需要将角度转化为弧度。
这样用for循环遍历5次,即得到了一个标准的五角星;
我们将这个过程封装成一个函数starPath(cxt)以便复用,其中参数cxt是绘图上下文环境
然而夜空中不可能只有一颗星星,而且天上的星星的排列应该是随机的,因此,我们需要采用Math中的random随机函数来随机星星的坐标与大小;
for (var i = 0; i < 200; i++) {
var r = Math.random()*5+5;
var x = Math.random()*canvas.width;
var y = Math.random()*canvas.height*0.65;
var a = Math.random()*360;
drawStar(cxt,r,x,y,a);
}
我们随机了星星的半径,坐标以及旋转角度,遍历200次,使天空中出现200颗星星
而其中绘制星星,我们也将其封装为一个函数drawStar(cxt,r,x,y,a),其中,r表示大圆半径,(x,y)为星星偏移量,a为星星的旋转角度
function drawStar(cxt,R,x,y,rot){//rot表示旋转角度
cxt.save();
cxt.translate(x,y);
cxt.rotate(rot/180*Math.PI);
cxt.scale(R,R);
starPath(cxt)
cxt.fillStyle = '#fb3';
cxt.fill();
cxt.restore();
}
这样,我们就绘制好了一片星空了
3.接下来,是绘制一轮月牙
(图片转自慕课网liuyubobobo讲师的课程Canvas绘图详解)
可以看出,这个月牙的外圆是一个1/2圆弧,易画出,可用arc()函数,然而内圆比较复杂,需要通过计算,再使用arcTo()函数画出。
实际实现中,为了使代码复用方便,我们绘制的是一个以(0,0)点为圆心,1为半径的一个月牙
function pathMoon(cxt, d){
cxt.beginPath();
cxt.arc(0, 0, 1, 0.5*Math.PI, 1.5*Math.PI, true);
cxt.moveTo(0, -1);
cxt.arcTo(d, 0, 0, 1, dis(0, -1, d, 0)/d);
cxt.closePath();
}
其中,参数d表示控制点,即图中C点的横坐标
在实现这个函数时,我们还封装了一个函数dis(x1,y1,x2,y2),用于求取直角三角形的斜边:
function dis(x1, y1, x2, y2){//直角三角形求取斜边
return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
这样,我们就画好了一个标准的月亮,而为了使这个月亮的大小位置颜色等属性满足我们的星空,还需要 进行完善,我们封装了一个函数fillMoon(cxt, d, x, y, R, rot, /*optional*/fillColor),其中,d表示控制点,(x,y)表示偏移量,R表示对月亮的缩放倍数,rot表示旋转角度,还有一个可选参数fillColor,表示月亮的颜色,如果没有给定,则用默认颜色#fb5
function fillMoon(cxt, d, x, y, R, rot, /*optional*/fillColor){
cxt.save();
cxt.translate(x,y);
cxt.rotate(rot*Math.PI/180);
cxt.scale(R,R);
pathMoon(cxt, d):
cxt.fillStyle = fillColor || '#fb5';
cxt.fill();
cxt.restore();
}
最后调用fillMoon函数,这样,我们的月亮也画好啦
4.如何绘制波浪形的绿地
我们已知,使用arcTo函数是没办法绘制波浪线的,贝塞尔二次曲线也无法绘制波浪线,因为这两个都只有一个控制点,因此这里我们只能用贝塞尔三次曲线来绘制波浪线。
贝塞尔三次曲线
context.moveTo(x0, y0); 起始点
context.bezierCurveTo(x1, y1, 控制点,
x2, y2, 控制点,
x3, y3); 结束点
与贝塞尔二次曲线不同,贝塞尔三次曲线有6个参数,分为两个控制点和一个结束点,因此,要绘制这片绿地,需要使用贝塞尔三次曲线。同时,这片绿地的背景色我们采用线性渐变色来处理,代码如下:
function drawLand(cxt){
cxt.save();
cxt.beginPath();
cxt.moveTo(0, 600);
cxt.bezierCurveTo(540, 400, 600, 800, 1200, 600);
cxt.lineTo(1200,800);
cxt.lineTo(0,800);
cxt.closePath();
var landStyle = cxt.createLinearGradient(0,800,0,0);
landStyle.addColorStop (0.0, '#030');
landStyle.addColorStop (1.0, '#580');
cxt.fillStyle = landStyle;
cxt.fill();
cxt.restore();
}
所有代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>星空练习</title>
</head>
<body>
<canvas id="canvas" style="display:block;border: 1px solid #aaa;margin: 50px auto">该浏览器不支持canvas,请更换浏览器后再试</canvas>
<script type="text/javascript">
window.onload = function(){
var canvas = document.getElementById('canvas');
canvas.width = 1200;
canvas.height = 800;
var cxt = canvas.getContext('2d');
//线性渐变背景
// var skyStyle = cxt.createLinearGradient(0,0,0,canvas.height);
// skyStyle.addColorStop(0.0, 'black');
// skyStyle.addColorStop(1.0, '#035');
// cxt.fillStyle = skyStyle;
// cxt.fillRect(0,0,canvas.width,canvas.height);
//径向渐变背景
var skyStyle = cxt.createRadialGradient(canvas.width/2,canvas.height,0,canvas.width/2,canvas.height,canvas.height);
skyStyle.addColorStop(0.0, '#035');
skyStyle.addColorStop(1.0, 'black');
cxt.fillStyle = skyStyle;
cxt.fillRect(0,0,canvas.width,canvas.height);
for (var i = 0; i < 200; i++) {
var r = Math.random()*5+5;//大圆半径为0-20
var x = Math.random()*canvas.width;
var y = Math.random()*canvas.height*0.65;
var a = Math.random()*360;
drawStar(cxt,r,x,y,a);
}
fillMoon(cxt, 2, 900, 200, 100, 30);
drawLand(cxt);
}
function drawLand(cxt){
cxt.save();
cxt.beginPath();
cxt.moveTo(0, 600);
cxt.bezierCurveTo(540, 400, 600, 800, 1200, 600);
cxt.lineTo(1200,800);
cxt.lineTo(0,800);
cxt.closePath();
var landStyle = cxt.createLinearGradient(0,800,0,0);
landStyle.addColorStop (0.0, '#030');
landStyle.addColorStop (1.0, '#580');
cxt.fillStyle = landStyle;
cxt.fill();
cxt.restore();
}
function drawStar(cxt,R,x,y,rot){//rot表示旋转角度
cxt.save();
cxt.translate(x,y);
cxt.rotate(rot/180*Math.PI);
cxt.scale(R,R);
starPath(cxt)
cxt.fillStyle = '#fb3';
// cxt.strokeStyle = 'fd5';
// cxt.lineWidth = 3;
// cxt.lineJoin = 'round';
cxt.fill();
//cxt.stroke();
cxt.restore();
}
function starPath(cxt){//绘制一个标准的五角星
cxt.beginPath();
for (var i = 0; i < 5; i++) {
cxt.lineTo(Math.cos( (18+i*72)/180*Math.PI),
-Math.sin((18+i*72)/180*Math.PI));
cxt.lineTo(Math.cos( (54+i*72)/180*Math.PI)*0.5,
-Math.sin((54+i*72)/180*Math.PI)*0.5);
}
cxt.closePath();
}
function fillMoon(cxt, d, x, y, R, rot, /*optional*/fillColor){
cxt.save();
cxt.translate(x,y);
cxt.rotate(rot*Math.PI/180);
cxt.scale(R,R);
pathMoon(cxt, d);
cxt.fillStyle = fillColor || '#fb5';
cxt.fill();
cxt.restore();
}
function pathMoon(cxt, d){
cxt.beginPath();
cxt.arc(0, 0, 1, 0.5*Math.PI, 1.5*Math.PI, true);
cxt.moveTo(0, -1);
cxt.arcTo(d, 0, 0, 1, dis(0, -1, d, 0)/d);
// cxt.quadraticCurveTo(1.2,0,0,1);利用贝塞尔二次曲线绘制
cxt.closePath();
}
function dis(x1, y1, x2, y2){//直角三角形求取斜边
return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
</script>
</body>
</html>
代码中还有线性渐变的背景色被注释掉了,感兴趣的小伙伴可以尝试一下线性背景色哦
补充:
对于月亮的绘制,其实不一定要用arcTo绘制,也可以尝试使用贝塞尔二次曲线来绘制
// cxt.quadraticCurveTo(1.2,0,0,1);利用贝塞尔二次曲线绘制,可以用这个代替arcTo曲线