前面讲了一些canvas的基础知识
下面就来一步步实现下面这个效果
分析一下这个效果:
第一部分:背景+颜色渐变(css)
第二部分:动(js)
<div class="landscape"></div> //背景
<div class="filter"></div> //遮罩层
<canvas id="canvas"></canvas> //canvas
第一部分:背景+渐变
背景往往是纯色的或者是渐变的,再或者就是有规律的可以平铺的图形。为了适配所有的设备,尽可能让所有的设备都能够显示出相同的效果,不能相差太远,这种情况就只能将背景设置为单一的颜色,或者两种颜色(渐变),再或者一些可平铺的图案,让其在各个终端下都有相同的显示效果。渐变取值可以参照uigradients
这个效果里面的背景是一张图片http://www.jq22.com/css/img/xkbg.png
颜色渐变可以用canvas和css来实现,这里就用css来实现
将 body 的颜色设置为黑色到蓝色的由上向下的渐变:
background: linear-gradient(to bottom,#000000 0%,#5788fe 100%);
背景颜色动态变化
设置一个全屏的遮罩,将这个遮罩的背景色设置为红色,然后使用 CSS3 的 animation 属性,使用 animation 改变其透明度,由 0 变为 0.9。
.filter {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: #fe5757;
animation: colorChange 30s ease-in-out infinite;//过渡时间30s,慢速开始和结束,无限次循环
animation-fill-mode: both;//规定填充模式为both
mix-blend-mode: overlay;元素的内容与元素的直系父元素的内容和元素的背景叠加
}
@keyframes colorChange {
0%, 100% {
opacity: 0;
}
50% {
opacity: .9;
}
}
到这里渐变的背景就完成了
第二部分:动(js)
随机粒子
看看这个效果
这些粒子都是随机生成的
- 第一步:创建全屏Canvas
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<style>
body{
margin:0;
width:100%;
height:100%;
overflow: hidden;
background-color: black;
cursor: pointer;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var WIDTH=document.documentElement.clientWidth;//获取屏幕的宽度
var HEIGHT=document.documentElement.clientHeight;//获取屏幕的高度
canvas.width=WIDTH;
canvas.height=HEIGHT;
</script>
</body>
</html>
- 第二步:设置 Round_item 类
在创建了一个全屏的 Canvas 之后,创建 Round_item 类。我们要设置的是位置随机、透明度随机、半径随机的圆。为了区分不同的圆,我们还应该设置一个唯一的 index 参数。
所以需要的参数有:
x 坐标
y 坐标
半径
透明度
index
function Round_item(index,x,y){
this.index=index;
this.x=x;
this.y=y;
this.r=Math.random()*2+1;
this.alpha=(Math.floor(random()*10)+1)/10/2;
this.color="rgba(255,255,255,"+alpha+")";
}
使用了构造函数的方式来创建单个的圆,我们还需要一个变量 initRoundPopulation 来设置 round 的个数,然后我们便可以通过 for 循环创建出 initRoundPopulation 个圆。
- 第三步:设置 draw() 方法
在设置了单个的 Round_item 类之后,我们还要给每一个 round 设置 draw() 方法,所以我们需要将 draw() 方法设置在 Round_item 的原型中,这样我们创建出来的每一个 Round_item 实例对象都拥有了 draw() 方法。
Round_item.prototype.draw=function(){
context.fillStyle=this.color;
context.shadowBlur=this.r*2;
context.beginPath();
context.arc(this.x,this.y,this.r,0,2*Math.PI,false);
context.closePath();
context.fill();
};
- 第四步:设置初始化 init() 函数
然后我们就需要设置初始化 init() 函数了,在 init() 函数中,我们的主要任务是创建单个的 round,然后使用其 draw() 方法。
function init(){
for(var i=0;i<initRoundPopulation;i++){
round[i]=new Round_item(i,Math.radom()*WIDTH,Math.radom()*HEIGHT);
Round[i].draw();
}
}
就这几步,随机粒子就生成了,完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<style>
html,body{
margin:0;
width:100%;
height:100%;
overflow: hidden;
background: black;
cursor: none;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var WIDTH=document.documentElement.clientWidth;
var HEIGHT=document.documentElement.clientHeight;
var initRoundPopulation=100;
var round=[];
canvas.width=WIDTH;
canvas.height=HEIGHT;
function Round_item(index,x,y){
this.index=index;
this.x=x;
this.y=y;
this.r=Math.random()*2+1;
var alpha=(Math.floor(Math.random()*10)+1)/10/2;
this.color="rgba(255,255,255,"+alpha+")";
}
Round_item.prototype.draw=function(){
context.fillStyle=this.color;
context.shadowBlur=this.r*2;
context.beginPath();
context.arc(this.x,this.y,this.r,0,2 * Math.PI,false);
context.closePath();
context.fill();
};
function init(){
for(var i=0;i<initRoundPopulation;i++){
round[i]=new Round_item(i,Math.random()*WIDTH,Math.random()*HEIGHT);
round[i].draw();
}
}
init();
</script>
</body>
</html>
随机粒子动起来
- animate() 函数
其实,Canvas 制作动画是一个不断擦除再重绘的过程,跟最原始实现动画的方式类似。在纸片上画每一帧,然后以很快的速度翻动小本本,就会有动画的效果。
现在我们实现动画需要在很短的时间内不断的清除内容再重新绘制,新的图形和原先清除的图形之间有某种位置关系,速度足够快的话,我们就会看到动画的效果。
animate() 函数,这个函数的作用是帮助我们形成动画,我们在这个函数中首先需要清除当前屏幕,这里的清除函数用到的是 content.clearRect() 方法。
看一下 canvas 的 content.clearRect() 方法:
context.clearRect(x,y,width,height);
x:要清除的矩形左上角的 x 坐标
y:要清除的矩形左上角的 y 坐标
width:要清除的矩形的宽度,以像素计
height:要清除的矩形的高度,以像素计
在刚刚的分析中可以得出,我们需要清除的区域是整个屏幕,所以 content.clearRect() 的参数就是 content.clearRect(0, 0, WIDTH, HEIGHT);,这里我们就用到了之前获取的屏幕宽度和高度的常量:WIDTH 和 HEIGHT。这样我们就将屏幕上的所有内容都清除了。
清除了屏幕内容之后我们就要重新绘制图形,重新绘制的图形是需要和原图形之间有一定的关系,我们先制作一个简单的效果 —— 粒子匀速上升。粒子匀速上升,也就是 y 坐标在不断地变化,既然是匀速的,那么也就是在相同的时间位移是相同的。我们将粒子位移的变化函数 move() 写在 Round_item 的原型上。
重新绘制完图形之后,我们就完成了清除屏幕内容再重新绘制新的图形的任务。那么还需要有一个步骤 —— “ 不断”,要想实现动画的效果,就需要 “不断” 地进行清除再重绘,并且中间间隔的时间还不能过长。
会想到 js 的 setTimeout() 方法,但是 setTimeout 和 setInterval 的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器 UI 线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。
使用这个函数 —— requestAnimationFrame() 。
window.requestAnimationFrame() 方法告诉浏览器,你希望执行动画,并请求浏览器调用指定的函数在下一次重绘之前更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。
requestAnimationFrame() 函数可以说是专门用来写动画的。那么 requestAnimationFrame() 有什么优点呢?
编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。
大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是 1000ms/60,约等于 16.6ms。
requestAnimationFrame 采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
所以我们就使用 requestAnimationFrame() 函数递归的调用 animate() 函数来实现动画的效果。
function animate() {
context.clearRect(0, 0, WIDTH, HEIGHT);
for (var i in round) {
round[i].move();
}
requestAnimationFrame(animate);
}
- 创建move()函数
将 move() 方法写在 Round_item 的原型上,这样我们创建的每一个 round 都具有了 move() 方法。
在 move() 方法中,我们只需要改变 round 的 y 坐标即可,并且设置边界条件,当 y 坐标的值小于 -10(也可以是其他负值),代表该 round 已经超出了屏幕,这个时候我们要将其移动到屏幕的最底端,这样才能保证我们创建的粒子数不变,一直是 initRoundPopulation 的值。
在 y 坐标的变化之后,我们还需要使用新的 y 坐标再来重新绘制一下该 round。
Round_item.prototype.move = function () {
this.y -= 0.15;
if (this.y <= -10) {
this.y = HEIGHT + 10;
}
this.draw();
};
- 在 init() 中加入 animate()
我们想要实现动画的效果,还需要在 init() 中加入 animate() 函数。
鼠标和屏幕互动
- onmousemove 事件
完整代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<style>
html,body{
margin:0;
width:100%;
height:100%;
overflow: hidden;
background: black;
cursor: none;
background: linear-gradient(to bottom, #000000 0%, #5788fe 100%);
}
.filter {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: #fe5757;
animation: colorChange 30s ease-in-out infinite;
animation-fill-mode: both;
mix-blend-mode: overlay;
}
@keyframes colorChange {
0%, 100% {
opacity: 0;
}
50% {
opacity: .9;
}
}
.landscape {
position: absolute;
bottom: 0px;
left: 0;
width: 100%;
height: 100%;
background: url('http://www.jq22.com/css/img/xkbg.png')center bottom repeat-x;
background-size: 1000px 250px;
}
</style>
</head>
<body>
<div class="landscape"></div>
<div class="filter"></div>
<canvas id="canvas"></canvas>
<script>
function Round_item(index,x,y){
this.index=index;
this.x=x;
this.y=y;
var r;
this.r=Math.random()*2+1;
var alpha=(Math.floor(Math.random()*10)+1)/10/2;
this.color="rgba(255,255,255,"+alpha+")";
}
Round_item.prototype.draw=function(){
context.fillStyle=this.color;
context.shadowBlur=this.r*2;
context.beginPath();
context.arc(this.x,this.y,this.r,0,2 * Math.PI,false);
context.closePath();
context.fill();
}
Round_item.prototype.move=function(){
this.y-=.25;
if(this.y<=0){
this.y=HEIGHT+10;
}
this.draw();
}
function Dot(id,x,y,r){
this.id=id;
this.x=x;
this.y=y;
this.r=Math.floor(Math.random()*5)+1;
this.maxLinks=2;
this.speed=.5;
this.a=.5;
this.aReduction = .005;
this.color = "rgba(255,255,255," + this.a + ")";
this.linkColor = "rgba(255,255,255," + this.a / 4 + ")";
this.dir = Math.floor(Math.random() * 140) + 200;
}
Dot.prototype.draw=function(){
context.fillStyle=this.color;
context.shadowBlur=this.r*2;
context.beginPath();
context.arc(this.x,this.y,this.r,0,2*Math.PI,false);
context.closePath();
context.fill();
}
Dot.prototype.link=function(){
if(this.id==0)return;
var previousDot1 = getPreviousDot(this.id, 1);
var previousDot2 = getPreviousDot(this.id, 2);
var previousDot3 = getPreviousDot(this.id, 3);
if(!previousDot1)return;
context.strokeStyle=this.linkColor;
context.moveTo(previousDot1.x,previousDot1.y);
context.beginPath();
context.lineTo(this.x,this.y);
if (previousDot2 != false) context.lineTo(previousDot2.x, previousDot2.y);
if (previousDot3 != false) context.lineTo(previousDot3.x, previousDot3.y);
context.stroke();
context.closePath();
}
function getPreviousDot(id,stepback){
if(id==0||id-stepback<0) return false;
if(typeof dots[id-stepback]!="undefined") return dots[id-stepback];
else return false;
}
Dot.prototype.move=function(){
this.a-=this.aReduction;
if(this.a<=0){
this.die();
}
this.color = "rgba(255,255,255," + this.a + ")";
this.linkColor = "rgba(255,255,255," + this.a / 4 + ")";
this.x = this.x + Math.cos(degToRad(this.dir)) * this.speed,
this.y = this.y + Math.sin(degToRad(this.dir)) * this.speed;
this.draw();
this.link();
}
Dot.prototype.die=function(){
dots[this.id] = null;
delete dots[this.id];
}
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var WIDTH,HEIGHT;
var initRoundPopulation=100;
var round=[];
var dots=[];
var mouseMoving=false;
var mouseMoveChecker,mouseX,MouseY;
var dotsMinDist=2,maxDistFromCursor = 50;
setCanvasSize();
init();
function setCanvasSize(){
WIDTH=document.documentElement.clientWidth;
HEIGHT=document.documentElement.clientHeight;
canvas.setAttribute("width",WIDTH);
canvas.setAttribute("height",HEIGHT);
}
function animate(){
context.clearRect(0,0,WIDTH,HEIGHT);
for(var i in round){
round[i].move();
console.log(i+","+round[i]+"\n");
}
for(var i in dots){
dots[i].move();
}
drawIfMouseMoving();
requestAnimationFrame(animate);
}
function drawIfMouseMoving(){
if(!mouseMoving) return;
if(dots.length==0){
dots[0]=new Dot(0,mouseX,mouseY);
dots[0].draw();
return;
}
var previousDot = getPreviousDot(dots.length, 1);
var prevX = previousDot.x;
var prevY = previousDot.y;
var diffX = Math.abs(prevX - mouseX);
var diffY = Math.abs(prevY - mouseY);
if (diffX < dotsMinDist || diffY < dotsMinDist) return;
var xVariation = Math.random() > .5 ? -1 : 1;
xVariation = xVariation * Math.floor(Math.random() * maxDistFromCursor) + 1;
var yVariation = Math.random() > .5 ? -1 : 1;
yVariation = yVariation * Math.floor(Math.random() * maxDistFromCursor) + 1;
dots[dots.length] = new Dot(dots.length, mouseX + xVariation, mouseY + yVariation);
dots[dots.length - 1].draw();
dots[dots.length - 1].link();
}
function degToRad(deg) {
return deg * (Math.PI / 180);
}
function init(){
context.strokeStyle="white";
context.shadowColor="white";
for(var i=0;i<initRoundPopulation;i++){
round[i]=new Round_item(i,Math.random()*WIDTH,Math.random()*HEIGHT);
//round[i].draw();
}
context.shadowBlur=0;
animate();
}
window.onmousemove = function (e) {
mouseMoving = true;
mouseX = e.clientX;
mouseY = e.clientY;
clearInterval(mouseMoveChecker);
mouseMoveChecker = setTimeout(function () {
mouseMoving = false;
}, 100);
}
</script>
</body>
</html>