由于最近工作没有使用前端,而且前面写的有些疲惫,就没再写。但是感觉一段时间不写之后就忘得差不多了,学习真是一件持之以恒的事情,所以打算继续写下去。
项目简介
一个在夜空中放烟花的特效,烟花绽放的效果还是很酷炫的,而且实现也不是很难,主要是用到画布和动画的相关知识。
烟花.gifhtml部分
该项目的html部分比较简单,正文部分只有一个画布(canvas)。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>烟花绽放</title>
<link rel="stylesheet" type="text/css" href="fireworks.css" />
</head>
<body>
<canvas id="canvas">Cancas is not supported in your browser</canvas>
<script src="fireworks.js"></script>
</body>
</html>
CSS部分
CSS主要是将背景渲染为黑色,看起来像夜空,然后将画布铺满整个页面。
body {
background: #000;
margin: 0;
}
canvas {
cursor: crosshair;
display: block;
}
javascript部分
本项目主要是通过javascript实现,所以这部分比较复杂,下面依次说明。
首先需要一个RequestAnimationFrame来实现动画,这个方法相比setTimeout和setInterval有很多的优势,主要体现在:
-
requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
-
在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量。
-
requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。
但是由于不同的浏览器中对此方法的实现不同,因此会存在一些兼容性问题,本文对其进行了一些简单的处理,更加优雅的实现大家可以自行查找资料。
//专门用于动画的API
window.requestAnimationFrame = ( function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ) {
window.setTimeout( callback, 1000 / 60 );
};
})();
下面定义一些变量和方法。
var canvas = document.getElementById('canvas'),
// 创建 context 对象
ctx = canvas.getContext('2d'),
// 获取窗体大小
cw = window.innerWidth,
ch = window.innerHeight,
// 烟花数组
fireworks = [],
// 烟花粒子数组
particles = [],
// 初始色调
hue = 120,
// 限制数量,防止产生太多烟花
limiterTotal = 5,
limiterTick = 0,
// 自动发射烟花时间间隔
timerTotal = 80,
timerTick = 0,
mousedown = false,
// 鼠标的x和y坐标
mx,
my;
// 设置画布大小
canvas.width = cw;
canvas.height = ch;
// 获取随机值方法
function random(min, max) {
var t = Math.random() * (max - min) + min;
return t;
}
// 计算两点之间的距离的方法
function calculateDistance(p1x, p1y, p2x, p2y) {
var xDistance = p1x - p2x,
yDistance = p1y - p2y;
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
本项目由2个类需要实现分蘖是烟花和烟花粒子。先看一下烟花粒子类。
// 烟花粒子类
function Particle(x, y) {
// 坐标
this.x = x;
this.y = y;
// 跟踪每个粒子的以前坐标以创建轨迹效果
// 如果要更好的效果,可以增加坐标数量
this.coordinates = [];
this.coordinatesCount = 5;
while(this.coordinatesCount--) {
this.coordinates.push([this.x, this.y]);
}
// 设置随机角度,随随机速度
this.angle = random(0, Math.PI * 2);
this.speed = random(1, 10);
// 摩擦力, 减慢下落速度
this.friction = 0.95
// 重力,让烟花坠落
this.gravity = 1;
// 调整色调为 +-20之间调整
this.hue = random(hue - 20, hue + 20);
// 亮度
this.brightness = random(50, 80);
this.alpha = 1;
//设置粒子消失速度
this.decay = random(0.015, 0.03);
}
// 更新烟花粒子
Particle.prototype.update = function(index) {
// 移除坐标数组中最后一个元素
this.coordinates.pop();
// 在坐标数组开头插入当前元素
this.coordinates.unshift([this.x, this.y]);
// 降低速度
this.speed *= this.friction;
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed + this.gravity;
// 淡化粒子
this.alpha -= this.decay;
// 如果透明度足够低,就根据索引移除粒子
if(this.alpha <= this.decay) {
particles.splice(index, 1);
}
}
// 绘制烟花粒子
Particle.prototype.draw = function() {
ctx.beginPath();
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0],
this.coordinates[this.coordinates.length - 1][1]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = 'hsla(' + this.hue + ', 100%, ' + this.brightness + '%, '
+ this.alpha + ')';
ctx.stroke();
}
本部分的一个知识点是使用画布canvas绘图。HTML5 中<canvas> 元素用于图形的绘制,通过脚本 (通常是JavaScript)来完成。
beginPath()方法表示要开始绘制一条路径;moveTo()方法定义开始坐标,lineTo()定义绘制一条直线且指明结束坐标。strokeStyle属性设置笔触样式,stroke()方法开始绘制。
strokeStyle属性设置或返回用于笔触的颜色、渐变或模式。这里使用HSLA表示色彩空间(还有一种方式是RGBA),HSLA指的是“色调、饱和度、亮度、Alpha透明度”:
- H:Hue(色调): 取值在0度~360度之间,0度是红色,120度是绿色,240度是蓝色,360度也是红色;
- S:Saturation(饱和度): 是色彩的纯度,是一个百分比的值,取值在0%~100%,0%饱和度最低,100%饱和度最高;
- L:Lightness(亮度): 取值在0%~100%,0%最暗,100%最亮;
- A:Alpha(透明度): 取值在0.0~1.0,0.0完全透明,1.0完全不透明。
还需要一个生成烟花例子的方法。
// 生成烟花粒子
function createParticles(x, y) {
var particleCount = 30;
while(particleCount--) {
particles.push(new Particle(x, y));
}
}
下面是烟花类。
//烟花类
function Fireworks(sx, sy, tx, ty) {
// 实际的烟花粒子位置
this.x = sx;
this.y = sy;
// 初始的烟花粒子
this.sx = sx;
this.sy = sy;
// 目标位置
this.tx = tx;
this.ty = ty;
// 开始位置到目标的距离
this.distanceToTarget = calculateDistance(sx, sy, tx, ty);
this.distanceTraveled = 0;
// 跟踪每个烟花的坐标以创建轨迹效果
this.coordinates = [];
this.coordinatesCount = 3;
while(this.coordinatesCount--) {
this.coordinates.push([this.x, this.y]);
}
this.angle = Math.atan2(ty - sy, tx - sx);
this.speed = 2;
this.acceleration = 1.05;
this.brightness = random(50, 70);
// 烟花绽放时候的圆半径
this.targetRadius = 1;
}
// 更新烟花
Fireworks.prototype.update = function(index) {
// 移除最后一个元素
this.coordinates.pop();
// 向数组开头添加一个元素
this.coordinates.unshift([this.x, this.y]);
if(this.targetRadius < 8) {
this.targetRadius += 0.3;
} else {
this.targetRadius = 1;
}
// 加速
this.speed *= this.acceleration;
//根据角度和速度获取当前速度
var vx = Math.cos(this.angle) * this.speed,
vy = Math.sin(this.angle) * this.speed;
// 移动距离
this.distanceTraveled = calculateDistance(this.sx, this.sy,
this.x + vx, this.y + vy);
// 移动距离大于目标距离,则已经到达
if(this.distanceTraveled >= this.distanceToTarget) {
createParticles(this.tx, this.ty);
fireworks.splice(index, 1);
} else {
this.x += vx;
this.y += vy;
}
}
// 绘制烟花
Fireworks.prototype.draw = function() {
ctx.beginPath();
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0],
this.coordinates[this.coordinates.length - 1][1]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = 'hsl(' + hue + ', 100%, ' + this.brightness + '%)';
ctx.stroke();
ctx.beginPath();
ctx.arc(this.tx, this.ty, this.targetRadius, 0, Math.PI * 2);
ctx.stroke();
}
利用RequestAnimationFrame不断地执行函数来实现不断释放烟花的效果。
// 循环释放烟花的函数
function loop() {
requestAnimationFrame(loop);
// 获取不同颜色的烟花
hue= random(0, 360);
// 通常,使用ClearRect()清除画布
// 但我们要创建一个尾随效果
// 所以将复合操作设置为destination out
// 这将允许我们在特定的不透明度下清除画布,而不是完全擦除画布
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, cw, ch);
//将复合操作改回主模式
//lighter创建明亮的亮点,因为烟花粒子会相互重叠
ctx.globalCompositeOperation = 'lighter';
// 循环每个烟花,绘制和更新
var i = fireworks.length;
while(i--) {
fireworks[i].draw();
fireworks[i].update(i);
}
i = particles.length;
while(i--) {
particles[i].draw();
particles[i].update(i);
}
// 鼠标未按下,每隔一段时间自动发射烟花
if(timerTick >= timerTotal) {
if(!mousedown) {
// 在屏幕中下方开始烟火,然后设置随机目标坐标,随机Y坐标将设置在屏幕上半部分的范围内
fireworks.push(new Fireworks(cw / 2, ch, random(0, cw), random(0, ch / 2)));
timerTick = 0;
}
} else {
timerTick++;
}
//限制鼠标按下时烟花发射的速率
if(limiterTick >= limiterTotal) {
if(mousedown) {
// 在屏幕中下方开始放烟火,然后将当前鼠标坐标设置为目标位置
fireworks.push(new Fireworks(cw / 2, ch, mx, my));
limiterTick = 0;
}
} else {
limiterTick++;
}
}
其中,globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。destination-out表示在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
下面绑定鼠标事件。
// 添加鼠标事件
//鼠标移动事件
canvas.addEventListener('mousemove', function(e) {
mx = e.pageX - canvas.offsetLeft;
my = e.pageY - canvas.offsetTop;
});
// 鼠标按下事件,防止画布被选中
canvas.addEventListener('mousedown', function(e){
e.preventDefault();
mousedown = true;
});
canvas.addEventListener('mouseup', function(e) {
e.preventDefault();
mousedown = false;
});
窗口加载完毕,自动开始释放烟花。
window.onload = loop;
网友评论