美文网首页
前端新手项目练习之烟花绽放

前端新手项目练习之烟花绽放

作者: 简单一点点 | 来源:发表于2019-05-02 10:13 被阅读0次

由于最近工作没有使用前端,而且前面写的有些疲惫,就没再写。但是感觉一段时间不写之后就忘得差不多了,学习真是一件持之以恒的事情,所以打算继续写下去。

项目简介

一个在夜空中放烟花的特效,烟花绽放的效果还是很酷炫的,而且实现也不是很难,主要是用到画布和动画的相关知识。

烟花.gif

html部分

该项目的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有很多的优势,主要体现在:

  1. requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。

  2. 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量。

  3. 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;

相关文章

  • 前端新手项目练习之烟花绽放

    由于最近工作没有使用前端,而且前面写的有些疲惫,就没再写。但是感觉一段时间不写之后就忘得差不多了,学习真是一件持之...

  • 前端新手项目练习之可拖动弹窗

    前端新手项目练习之可拖动弹窗 前端新手记录自己在网络上找到的前端练习项目。最近工作忙得要死,感觉自己有些扛不住,这...

  • 前端新手项目练习之钟表

    前端新手项目练习值钟表 前端新手记录自己在网络上找到的前端练习项目。 项目简介 一个简单的钟表,有刻度和数字,利用...

  • 前端新手项目练习之星级评分

    前端新手项目练习之星级评分 前端新手记录自己在网络上找到的前端练习项目。 项目简介 一个简单的星评分系统,鼠标移上...

  • 前端新手项目练习之网页换肤

    前端新手记录自己在网络上找到的前端练习项目。 项目简介 项目包含红、绿、黑3个简单的按钮和一个导航栏,通过点击按钮...

  • 前端新手项目练习之广告轮播

    前端新手记录自己在网络上找到的前端练习项目。 项目简介 5个广告轮流播放,鼠标放上去会停止切换,鼠标移开继续轮播,...

  • 前端新手项目练习之select控件

    前端新手记录自己在网络上找到的前端练习项目。昨天去通州考试,早上五点出发赶第一班地铁,真是累的要死。 项目简介 一...

  • 前端新手项目练习之拼图游戏

    前端新手记录自己在网络上找到的前端练习项目。趁热打铁,再练习一下鼠标拖动相关的事件,这个小游戏是一个不错的选择,玩...

  • 前端新手项目练习之星星闪烁

    前端新手记录自己在网络上找到的前端练习项目。 项目简介 黑色背景的网页上随机分布着一个个闪烁的星星,星星闪烁的效果...

  • 剑与剑 之 一剑红粉美人行 第十章

    花。 烟花。 炫美烟花。 于女人之青春,如那炫美苍穹的烟花。 似一生之璀璨,都映在那绽放的一刹。 廖廖几岁青春,美...

网友评论

      本文标题:前端新手项目练习之烟花绽放

      本文链接:https://www.haomeiwen.com/subject/ebtnnqtx.html