最近在学习canvas
,掌握了理论知识后,以星空图为例实践下。示例样式
1、画背景布
<canvas>
是HTML的一个标签,可以直接设置width
和height
html:
<canvas id="myCanvas">
Your browser does not support HTML5 Canvas.//添加文本,用于不支持canvas的浏览器
</canvas>
在js中,也可以直接通过document.createElement('canvas')
创建<canvas>
元素,并通过appendChild('canvas')
插入。
引用并且初始化背景布 js:
var canvas = document.getElementById('myCanvas');//引用canvas元素
var ctx= canvas.getContext('2d');//获取2D环境
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;//使canvas画布的大小为整个屏幕的大小
2、画星星
画星星我们需要另外的canvas元素,而且这个元素应该是动态创建的
var canvas2=document.createElement('canvas');//创建另外的canvas元素
var ctx2=canvas2.getContext('2d');
绘制圆形
星星的绘制基础图形就是圆形,需要用到arc()
- 角度和弧度
在
css
的旋转中,用到的是角度(deg
),而canvas
中用到的是弧度(rad
)
rad = (π / 180) * deg //2π即为一周的弧度
deg = (rad * 180) / π
在js中π
即Math.PI
- 绘制圆和圆弧
arc(x, y, radius, startRad, endRad, [anticlockwise]);
其中:
(x,y)
:绘制的圆/圆弧的圆心坐标;
radius
:半径
startRad
:起始弧度;
endRad
:终止弧度;
anticlockwise
:绘制方向,true
逆时针,false
逆时针(默认);
绘制圆形时,startRad=0,endRad=2π
ctx2.arc(50, 50, 50, 0, Math.PI * 2);//画一个圆(x,y,半径,起始弧度,结束弧度)
渐变
示例中的星空是有渐变效果的,canvas
中的渐变分为两种,线性渐变 和 径向渐变:
- 线性渐变
ctx.createLinearGradient(x1,y1,x2,y2);//(x1,y1)渐变起始点,(x2,y2)渐变终点
当x1=x2,y1≠y2
垂直的线性渐变;
当x1≠x2,y1=y2
水平的线性渐变;
当x1≠x2,y1≠y2
角度的线性渐变;
- 径向渐变
ctx.createRadialGradient(x1,y1,r1,x2,y2,r2);//起始圆:圆心(x1,y1) 半径r1;结束圆:圆心(x2,y2) 半径r2
- 添加渐变颜色
gradient.addColorStop(position,color)
position
:指定渐变中颜色所在的相对位置(0~1);
color
:指定渐变中的颜色
- 填充渐变
ctx.fillStyle=gradient;
画一个渐变颜色的星星
var gradient=ctx2.createRadialGradient(50,50,0,50,50,50);
gradient.addColorStop(0,'#fff');
gradient.addColorStop(0.1, 'hsl(217, 61%, 33%)');
gradient.addColorStop(0.25, 'hsl(217, 64%, 6%)');
gradient.addColorStop(1, 'transparent');
ctx2.fillStyle = gradient;
ctx2.beginPath();
ctx2.arc(50, 50, 50, 0, Math.PI * 2);//画一个圆(x,y,半径,起始弧度,结束弧度)
ctx2.fill();
- 渲染到画布上
ctx.drawImage(canvas2, 200,200, 50, 50);//在canvas绘制canvas2(img,x,y,w,h)
此时你会得到一颗星星
- 很多颗星星 将渲染放在循环中,位置由随机数生成,你会得到很多颗星星
var starNum=1000;//星星数量
function drawStar(){
for(let i=0;i<starNum;i++){
let starX=Math.random()*w;
let starY=Math.random()*h;//随机生成星星的位置
ctx.drawImage(canvas2, starX,starY, 50, 50);//在canvas绘制canvas2(img,x,y,w,h)
}
}
drawStar();
星星的随机性
而不难发现示例中的星空,为了真实的效果,四周的星星大一些,中间的星星小一些
因此我们的随机数里除了位置随机,星星的w
和h
也应当遵从一定的规律
分析:我们看到星星沿着星轨运动可以看作是沿着一个一个的同心圆运动
如上图,我们找到屏幕的视觉中心,越靠近里面的运动越慢,星星直径越小,越靠近外面的,直径越大,运动越快,当然为了真实一点,用随机数来取。
参考了很多大佬的算法,得出如下结果:
- 首先取到屏幕中心点距离,即最外层圆(最大轨道)的半径
function maxOrbit(x, y) {
var max = Math.max(x, y),
diameter=Math.round(Math.sqrt(x*x+y*y))
return diameter / 2;//矩形对角线的一半
}
//在[min,max]随机取一个整数(包括min,max)
function random(min, max) {
if (arguments.length < 2) {
max = min;
min = 0;
}//如果只传了一个min,则max取min,min取0
if (min > max) {
var hold = max;
max = min;
min = hold;
}//min>max,互换
return Math.floor(Math.random() * (max - min + 1)) + min;
}
- 为了使其是以一个同心圆散开,因此定位
x/y
是以屏幕w/h
的一半+/-
一个在0到最大圆半径之间的随机数的值。可以借助三角函数sin()/cos()
,其周期是-1~1
之间
var orbitRadius = random(maxOrbit(w, h));//[0,最大轨道半径]之间取随机数,这个随机数作为基础的随机变量
var orbitX = w / 2;//屏幕宽的一半
var orbitY = h / 2;//屏幕高的一半
function drawStar(){
for(let i=0;i<starNum;i++){
var starX = Math.sin(i) * orbitRadius + orbitX,//sin和cos可能只是想要个-1~1之间的随机数
starY = Math.cos(i) * orbitRadius + this.orbitY,//orbitRadius越大,越靠近边缘,w和h越有可能大,因此大的星星都在四周(也存在小的)
ctx.drawImage(canvas2, starX,starY, 50, 50);//在canvas绘制canvas2(img,x,y,w,h)
}
}
drawStar();
此时可以得到一个随机的圆环
- 星星的大小,星星的w/h也改为随机数
var radius = random(60, this.orbitRadius) / 12;//至于这个变量依据什么,我只能说:借鉴
function drawStar(){
for(let i=0;i<starNum;i++){
var starX = Math.sin(i) * orbitRadius + orbitX,//sin和cos只是想要个-1~1之间的随机数
starY = Math.cos(i) * orbitRadius + this.orbitY,//orbitRadius越大,越靠近边缘,w和h越有可能大,因此大的星星都在四周(因为取随机数,也存在小的)
ctx.drawImage(canvas2, starX,starY, radius, radius);//在canvas绘制canvas2(img,x,y,w,h)
}
}
drawStar();
此时我们得到一个近大远小的圆环
- 重复调用drawStar,利用
window.requestAnimationFrame()
画出多层
var orbitX = w / 2;//屏幕宽的一半
var orbitY = h / 2;//屏幕高的一半
var orbitRadius,radius;
function drawStar(){
orbitRadius = random(maxOrbit(w, h));//[0,最大轨道半径]之间取随机数
radius=random(60, this.orbitRadius) / 12;//
for(let i=0;i<starNum;i++){
var starX = Math.sin(i) * orbitRadius + orbitX,//sin和cos可能只是想要个-1~1之间的随机数
starY = Math.cos(i) * orbitRadius + orbitY;//orbitRadius越大,越靠近边缘,w和h越有可能大,因此大的星星都在四周(也存在小的)
ctx.drawImage(canvas2, starX,starY, radius, radius);//在canvas绘制canvas2(img,x,y,w,h)
}
window.requestAnimationFrame(drawStar);
}
drawStar();
此时,虽然已经达到了想要的效果,还是不像星空,因为,太规律了,因此我们需要加一些随机因素
- 随机因素,就在于,
sin()
和cos()
里的数字我们取的是循环i
,这样它就会规律地出现位置,我们把i
换位[0,starNum]
中的一个随机数
var orbitX = w / 2;//屏幕宽的一半
var orbitY = h / 2;//屏幕高的一半
var orbitRadius,radius,timePassed;
function drawStar(){
orbitRadius = random(maxOrbit(w, h));//[0,最大轨道半径]之间取随机数
radius=random(60, this.orbitRadius) / 12;
starCount = random(0, starNum)
for(let i=0;i<starNum;i++){
var starX = Math.sin(starCount) * orbitRadius + orbitX,//sin和cos可能只是想要个-1~1之间的随机数
starY = Math.cos(starCount) * orbitRadius + orbitY;//orbitRadius越大,越靠近边缘,w和h越有可能大,因此大的星星都在四周(也存在小的)
ctx.drawImage(canvas2, starX,starY, radius, radius);//在canvas绘制canvas2(img,x,y,w,h)
}
window.requestAnimationFrame(drawStar);
}
drawStar();
你会得到一个闪烁的星空图(是在不断绘制新的星星)
星空动起来
原理:以上的设计,每次调用window.requestAnimationFrame()
实际只画了一颗星星,而想让其动起来,每次是画了1000颗星星,调用window.requestAnimationFrame()
后,重新画1000颗星星,以达到旋转的效果
- 改进,我们将drawStar()封装到原型,而只在animate调用时使用
window.requestAnimationFrame()
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
body {
background:#060e1b;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="text/javascript">
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
w = canvas.width = window.innerWidth,
h = canvas.height = window.innerHeight,
hue = 217,
stars = [],
count = 0,
maxStars = 1400;
var canvas2 = document.createElement('canvas'),
ctx2 = canvas2.getContext('2d');
canvas2.width = 100;
canvas2.height = 100;
var half = canvas2.width / 2,
gradient2 = ctx2.createRadialGradient(half, half, 0, half, half, half);
gradient2.addColorStop(0.025, '#fff');
gradient2.addColorStop(0.1, 'hsl(' + hue + ', 61%, 33%)');
gradient2.addColorStop(0.25, 'hsl(' + hue + ', 64%, 6%)');
gradient2.addColorStop(1, 'transparent');
ctx2.fillStyle = gradient2;
ctx2.beginPath();
ctx2.arc(half, half, half, 0, Math.PI * 2);//画一个圆(x,y,半径,起始弧度,结束弧度)
ctx2.fill();
function random(min, max) {
if (arguments.length < 2) {
max = min;
min = 0;
}//如果只传了一个min,则max取min,min取0
if (min > max) {
var hold = max;
max = min;
min = hold;
}//min>max,互换
return Math.floor(Math.random() * (max - min + 1)) + min;//在[min,max]随机取一个整数(包括min,max)
}
function maxOrbit(x, y) {
var max = Math.max(x, y),
// diameter = Math.round(Math.sqrt(max * max + max * max));//Math.round取整
diameter=Math.round(Math.sqrt(x*x+y*y))
return diameter / 2;//矩形对角线的一半
}
var Star = function() {
this.orbitRadius = random(maxOrbit(w, h));
this.radius = random(60, this.orbitRadius) / 12;
this.orbitX = w / 2;
this.orbitY = h / 2;
this.timePassed = random(0, maxStars);
this.speed = random(this.orbitRadius) / 50000;
this.alpha = random(2, 10) / 10;//0.2-1之间的透明度
count++;
stars[count] = this;
}
Star.prototype.draw = function(i) {
var x = Math.sin(this.timePassed) * this.orbitRadius + this.orbitX,//sin和cos可能只是想要个-1~1之间的随机数
y = Math.cos(this.timePassed) * this.orbitRadius + this.orbitY,//orbitRadius越大,越靠近边缘,w和h越有可能大,因此大的星星都在四周(也存在小的)
twinkle = random(10);
// 增加一些闪烁的效果
if (twinkle === 1 && this.alpha > 0) {
this.alpha -= 0.05;
} else if (twinkle === 2 && this.alpha < 1) {
this.alpha += 0.05;
}
ctx.globalAlpha = this.alpha;//设置图像的透明度
ctx.drawImage(canvas2, x - this.radius / 2, y - this.radius / 2, this.radius, this.radius);//在canvas绘制canvas2(img,x,y,w,h)
this.timePassed += this.speed;//转起来
}
for (var i = 0; i < maxStars; i++) {
new Star();
}
function animation() {
ctx.globalCompositeOperation = 'source-over';//图像合成,源图形不透明地方显示源图形,其余显示目标图形
ctx.globalAlpha = 0.8;//图像透明度
ctx.fillStyle = 'hsla(' + hue + ', 64%, 6%, 1)';
ctx.fillRect(0, 0, w, h)
ctx.globalCompositeOperation = 'lighter';//显示源图像+目标图像
for (var i = 1, l = stars.length; i < l; i++) {
stars[i].draw(i);
};
window.requestAnimationFrame(animation);
}
animation();
</script>
</body>
</html>
canvas图像合成
合成是指如何精细控制画布上对象的透明度和分层效果。在默认情况之下,如果在Canvas之中将某个物体(源)绘制在另一个物体(目标)之上,那么浏览器就会简单地把源特体的图像叠放在目标物体图像上面。
globalAlpha
:设置图像的透明度。默认值为1,表示完全不透明,这个值必须设置在图像绘制之前。globalCompositeOperation
:该属性的值在globalAlpha
以及所有变换都生效后控制当前canvas
位图中绘制图形。(共有26个值)可参考此链接
其中以上代码中:
source-over
:(默认值)源图形覆盖目标图形,源图形不透明的地方显示源图形,其余显示目标图形
lighter
:两图形中重叠部分作加色处理
最终效果: