JavaScript实现烟花绽放动画效果


Posted in Javascript onAugust 04, 2020

      先编写一个烟花绽放的动画效果。

      放烟花时,一个烟花可分为两个阶段:(1)烟花上升到空中;(2)烟花炸开成碎片,炸开的碎片慢慢消散。

      为此抽象出两个对象类:Firework和Particle。其中,Firework用于表示一个烟花对象,Particle用于表示一个烟花炸开后的各碎片。

      Firework对象类定义6个属性:表示烟花上升轨迹中各点的坐标(x,y)、烟花弧状轨迹的偏转角度angle、上升阶段水平和垂直方向的位移改变量xSpeed和ySpeed、烟花的色彩色相hue。

      坐标属性值y的初始值取画布的高度,表示烟花从地面上升到空中,其余各属性的初始值采用随机数确定。具体定义如下:

function Firework()

  {

   this.x = canvas.width/4*(1+3*Math.random());

   this.y = canvas.height - 15;

   this.angle = Math.random() * Math.PI / 4 - Math.PI / 6;

   this.xSpeed = Math.sin(this.angle) *(6+Math.random()*7);

   this.ySpeed = -Math.cos(this.angle) *(6+Math.random()*7);

   this.hue = Math.floor(Math.random() * 360);

  }

      Firework对象类定义3个方法:绘制烟花上升轨迹的方法draw()、烟花上升时坐标改变方法update()和烟花炸开方法explode()。绘制烟花轨迹时,在各点(x,y)处绘制一个宽度为5、高度为15的填充小矩形表示一个轨迹点。烟花上升时,垂直方向速度ySpeed初始值为负的,每次上升时,ySpeed加上一个正值,表示上升在减速,当ySpeed的值大于0时,烟花上升到顶了(不能再上升),就炸开为70个碎片。具体方法的实现见后面的HTML文件内容。

       Particle对象类定义8个属性:表示碎片散开轨迹中各点的坐标(x,y)、碎片弧状轨迹的偏转角度angle、散开时水平和垂直方向的位移改变量xSpeed和ySpeed、碎片的色彩色相hue、表示碎片小圆的半径size、碎片的亮度lightness。

function Particle(x,y,hue)

  {

   this.x = x;

   this.y = y;

   this.hue = hue;

   this.lightness = 50;

   this.size = 15 + Math.random() * 10;

   this.angle = Math.random() * 2 * Math.PI;

   this.xSpeed = Math.cos(this.angle) *(1+Math.random() * 6);

   this.ySpeed = Math.sin(this.angle) *(1+Math.random() * 6);

  }

       Particle对象类定义2个方法:绘制碎片散开轨迹的方法draw()、碎片散开时坐标改变方法update()。碎片散开时逐渐变小(属性size值减量),当size值小于1时,从碎片数组中删除该碎片,表示碎片已消亡。

       定义两个数组var fireworks=[];和var particles=[];分别存储烟花对象和炸开的碎片对象。

       模拟动画的函数loop中,每隔一段时间(用count计数来实现)向fireworks数组中添加一个烟花对象,烟花对象上升到顶炸开后,从fireworks数组中删除该对象元素,然后向particles数组中添加70个碎片对象。

      遍历两个数组的各对象,分别调用它们的draw()和update()方法。

编写的完整HTML文件内容如下。

<html> 
<head> 
<title>烟花绽放</title> 
</head>
<body>
<canvas id="myCanvas" width="800" height="600" style="border:3px double #996633;background:black;">
</canvas>
<script type="text/javascript">
  var canvas=document.getElementById('myCanvas');
  ctx= canvas.getContext('2d');
  var fireworks=[];
  var particles=[];
  var counter = 0;

  function Firework()
  {
   this.x = canvas.width/4*(1+3*Math.random());
   this.y = canvas.height - 15;
   this.angle = Math.random() * Math.PI / 4 - Math.PI / 6;
   this.xSpeed = Math.sin(this.angle) *(6+Math.random()*7);
   this.ySpeed = -Math.cos(this.angle) *(6+Math.random()*7);
   this.hue = Math.floor(Math.random() * 360);
  }
  Firework.prototype.draw= function() 
  {
   ctx.save();
   ctx.translate(this.x, this.y);
   ctx.rotate(Math.atan2(this.ySpeed, this.xSpeed) + Math.PI / 2);
   ctx.fillStyle =`hsl(${this.hue}, 100%, 50%)`;
   ctx.fillRect(0, 0, 5, 15);
   ctx.restore();
  }
  Firework.prototype.update= function() 
  {
   this.x = this.x + this.xSpeed;
   this.y = this.y + this.ySpeed;
   this.ySpeed += 0.1;
  }
  Firework.prototype.explode= function() 
  {
    for (var i = 0; i < 70; i++) 
    {
     particles.push(new Particle(this.x, this.y, this.hue));
    }
  }

  function Particle(x,y,hue) 
  {
   this.x = x;
   this.y = y;
   this.hue = hue;
   this.lightness = 50;
   this.size = 15 + Math.random() * 10;
   this.angle = Math.random() * 2 * Math.PI;
   this.xSpeed = Math.cos(this.angle) *(1+Math.random() * 6);
   this.ySpeed = Math.sin(this.angle) *(1+Math.random() * 6);
  }
  Particle.prototype.draw= function() 
  {
    ctx.fillStyle = `hsl(${this.hue}, 100%, ${this.lightness}%)`;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.fill();
  }
  Particle.prototype.update= function(index) 
  {
    this.ySpeed += 0.05;
    this.size = this.size*0.95;
    this.x = this.x + this.xSpeed;
    this.y = this.y + this.ySpeed;
    if (this.size<1) 
    {
      particles.splice(index,1);
    }
  }
  function loop() 
  {
   ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
   ctx.fillRect(0,0,canvas.width,canvas.height);
   counter++;
   if (counter==15) 
   {
     fireworks.push(new Firework());
     counter=0;
   }
   var i=fireworks.length;
   while (i--) 
   {
     fireworks[i].draw();
     fireworks[i].update();
     if (fireworks[i].ySpeed > 0) 
     {
       fireworks[i].explode();
       fireworks.splice(i, 1);
     }
   }
   var i=particles.length;
   while (i--) 
   {   
     particles[i].draw();
     particles[i].update(i);
   }
   requestAnimationFrame(loop);
  }
 loop();
</script>
</body> 
</html>

      在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中呈现出如图所示的烟花绽放动画效果。

JavaScript实现烟花绽放动画效果

      实现了烟花绽放的效果,我们还可以继续让一定区域内的绽放的烟花碎片拼成“Happy New Year”粒子文本。

      编写如下的HTML代码。

<html> 
<head> 
<title>迎新年烟花绽放</title> 
<style>
 body { margin: 0; background: black; }
 canvas { position: absolute; }
</style>
</head>
<body>
<canvas id="myCanvas1"></canvas>
<canvas id="myCanvas2"></canvas>
<canvas id="myCanvas3"></canvas>
<script type="text/javascript">
  function Particle(x, y, hue)
  {
   this.x = x;
   this.y = y;
   this.hue = hue;
   this.lightness = 50;
   this.size = 15 + Math.random() * 10;
   this.angle = Math.random() * 2 * Math.PI;
   this.xSpeed = Math.cos(this.angle) * (1 + Math.random() * 6);
   this.ySpeed = Math.sin(this.angle) * (1 + Math.random() * 6);
   this.target = getTarget();
   this.timer = 0;
  }
  Particle.prototype.draw= function() 
  {
   ctx2.fillStyle =`hsl(${this.hue}, 100%, ${this.lightness}%)`;
   ctx2.beginPath();
   ctx2.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
   ctx2.closePath();
   ctx2.fill();
  }
  Particle.prototype.update= function(idx) 
  {
   if (this.target) 
   {
     var dx = this.target.x - this.x;
     var dy = this.target.y - this.y;
     var dist = Math.sqrt(dx * dx + dy * dy);
     var a = Math.atan2(dy, dx);
     var tx = Math.cos(a) * 5;
     var ty = Math.sin(a) * 5;
     this.size = lerp(this.size, 1.5, 0.05);
     if (dist < 5) 
     {
       this.lightness = lerp(this.lightness, 100, 0.01);
       this.xSpeed = this.ySpeed = 0;
       this.x = lerp(this.x, this.target.x + fidelity / 2, 0.05);
       this.y = lerp(this.y, this.target.y + fidelity / 2, 0.05);
       this.timer += 1;
     }
     else if (dist < 10) 
     {
       this.lightness = lerp(this.lightness, 100, 0.01);
       this.xSpeed = lerp(this.xSpeed, tx, 0.1);
       this.ySpeed = lerp(this.ySpeed, ty, 0.1);
       this.timer += 1;
     } 
     else
     {
       this.xSpeed = lerp(this.xSpeed, tx, 0.02);
       this.ySpeed = lerp(this.ySpeed, ty, 0.02);
     }
   } 
   else
   {
     this.ySpeed += 0.05;
     this.size = this.size*0.95;
     if (this.size<1) 
     {
       particles.splice(idx,1);
     }
   }
   this.x = this.x + this.xSpeed;
   this.y = this.y + this.ySpeed;
  }

  function Firework() 
  {
   this.x = canvas2.width*(1+ 3*Math.random())/4;
   this.y = canvas2.height - 15;
   this.angle = Math.random() * Math.PI / 4 - Math.PI / 6;
   this.xSpeed = Math.sin(this.angle) * (6 + Math.random() * 7);
   this.ySpeed = -Math.cos(this.angle) * (6 + Math.random() * 7);
   this.hue = Math.floor(Math.random() * 360);
  }
  Firework.prototype.draw= function() 
  {
   ctx2.save();
   ctx2.translate(this.x, this.y);
   ctx2.rotate(Math.atan2(this.ySpeed, this.xSpeed) + Math.PI / 2);
   ctx2.fillStyle = `hsl(${this.hue}, 100%, 50%)`;
   ctx2.fillRect(0, 0, 5, 15);
   ctx2.restore();
  }
  Firework.prototype.update= function() 
  {
   this.x = this.x + this.xSpeed;
   this.y = this.y + this.ySpeed;
   this.ySpeed += 0.1;
  }
  Firework.prototype.explode= function() 
  {
   for (var i = 0; i < 70; i++) 
   {
     particles.push(new Particle(this.x, this.y, this.hue));
   }
  }

  function lerp(a, b, t)
  {
    return Math.abs(b - a)> 0.1 ? a + t * (b - a) : b;
  }
  function getTarget() 
  {
    if (targets.length > 0) 
    {
      var idx = Math.floor(Math.random() * targets.length);
      var { x, y } = targets[idx];
      targets.splice(idx, 1);
      x += canvas2.width / 2 - textWidth / 2;
      y += canvas2.height / 2 - fontSize / 2;
      return { x, y };
    }
  }

  var canvas1=document.getElementById('myCanvas1');
  ctx1= canvas1.getContext('2d');
  var canvas2=document.getElementById('myCanvas2');
  ctx2= canvas2.getContext('2d');
  var canvas3=document.getElementById('myCanvas3');
  ctx3= canvas3.getContext('2d');
  var fontSize = 200;
  var fireworks = [];
  var particles = [];
  var targets = [];
  var fidelity = 3;
  var counter = 0;
  canvas2.width = canvas3.width = window.innerWidth;
  canvas2.height = canvas3.height = window.innerHeight;
  ctx1.fillStyle = '#000';
  var text = 'Happy New Year';
  var textWidth = 999999;
  while (textWidth > window.innerWidth) 
  {
   ctx1.font = `900 ${fontSize--}px Arial`;
   textWidth = ctx1.measureText(text).width;
  }
  canvas1.width = textWidth;
  canvas1.height = fontSize * 1.5;
  ctx1.font = `900 ${fontSize}px Arial`;
  ctx1.fillText(text, 0, fontSize);
  var imgData = ctx1.getImageData(0, 0, canvas1.width, canvas1.height);
  for (var i = 0, max = imgData.data.length; i < max; i += 4) 
  {
    var alpha = imgData.data[i + 3];
    var x = Math.floor(i / 4) % imgData.width;
    var y = Math.floor(i / 4 / imgData.width);
    if (alpha && x % fidelity === 0 && y % fidelity === 0) 
    {
      targets.push({ x, y });
    }
  }
  ctx3.fillStyle = '#FFF';
  ctx3.shadowColor = '#FFF';
  ctx3.shadowBlur = 25;

  function loop() 
  {
   ctx2.fillStyle = "rgba(0, 0, 0, .1)";
   ctx2.fillRect(0, 0, canvas2.width, canvas2.height);
   counter += 1;
   if (counter==15) 
   {
     fireworks.push(new Firework());
     counter=0;
   }
   var i=fireworks.length;
   while (i--) 
   {
     fireworks[i].draw();
     fireworks[i].update();
     if (fireworks[i].ySpeed > 0) 
     {
       fireworks[i].explode();
       fireworks.splice(i, 1);
     }
   }
   var i=particles.length;
   while (i--) 
   {   
     particles[i].draw();
     particles[i].update(i);
     if (particles[i].timer >= 100 || particles[i].lightness >= 99) 
     {
       ctx3.fillRect(particles[i].target.x, particles[i].target.y, fidelity + 1, fidelity + 1);
       particles.splice(i, 1);
     }
   }
   requestAnimationFrame(loop);
  }
  loop();
</script>
</body> 
</html>

      在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中呈现出如图所示的烟花绽放迎新年动画效果。图2中为了控制图片的大小,删除了大量的中间帧,因此和实际运行的效果有所不同。

JavaScript实现烟花绽放动画效果

以上就是JavaScript实现烟花绽放动画效果的详细内容,更多关于JavaScript动画效果的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
js form 验证函数 当前比较流行的错误提示
Jun 23 Javascript
js Select下拉列表框进行多选、移除、交换内容的具体实现方法
Aug 13 Javascript
关于页面嵌入swf覆盖div层的问题的解决方法
Feb 11 Javascript
js实现网页标题栏闪烁提示效果实例分析
Nov 20 Javascript
JavaScript修改浏览器tab标题小技巧
Jan 06 Javascript
jquery动态添加删除(tr/td)
Feb 09 Javascript
IE及IE6浏览器中判断JS文件加载成功失败的方法
Feb 18 Javascript
Bootstrap CSS布局之表格
Dec 17 Javascript
jQuery使用unlock.js插件实现滑动解锁
Apr 04 jQuery
jquery.form.js异步提交表单详解
Apr 25 jQuery
Bootstrap Table使用整理(四)之工具栏
Jun 09 Javascript
浅析vue插槽和作用域插槽的理解
Apr 22 Javascript
JS事件循环机制event loop宏任务微任务原理解析
Aug 04 #Javascript
解决vue addRoutes不生效问题
Aug 04 #Javascript
vue 解决addRoutes多次添加路由重复的操作
Aug 04 #Javascript
Vue优化:常见会导致内存泄漏问题及优化详解
Aug 04 #Javascript
Jquery cookie插件实现原理代码解析
Aug 04 #jQuery
解决vue自定义指令导致的内存泄漏问题
Aug 04 #Javascript
vue中的v-model原理,与组件自定义v-model详解
Aug 04 #Javascript
You might like
使用PHP函数scandir排除特定目录
2014/06/12 PHP
php实现计数器方法小结
2015/01/05 PHP
是 WordPress 让 PHP 更流行了 而不是框架
2016/02/03 PHP
PHP实现在windows下配置sendmail并通过mail()函数发送邮件的方法
2017/06/20 PHP
Laravel框架实现redis集群的方法分析
2017/09/14 PHP
PHP设计模式之适配器模式原理与用法分析
2018/04/25 PHP
php中html_entity_decode实现HTML实体转义
2018/06/13 PHP
phpQuery采集网页实现代码实例
2020/04/02 PHP
jQuery select控制插件
2009/08/17 Javascript
javascript 系统文件夹文件操作及参数介绍
2013/01/08 Javascript
jquery实现拖拽调整Div大小
2015/01/30 Javascript
JavaScript Function函数类型介绍
2015/04/08 Javascript
10条建议帮助你创建更好的jQuery插件
2015/05/18 Javascript
javascript中对Date类型的常用操作小结
2016/05/19 Javascript
浅谈键盘上回车按钮的js触发事件
2017/02/13 Javascript
Vue.js鼠标悬浮更换图片功能
2017/05/17 Javascript
浅谈Angular2 ng-content 指令在组件中嵌入内容
2017/08/18 Javascript
Angular.js通过自定义指令directive实现滑块滑动效果
2017/10/13 Javascript
使用JQuery自动完成插件Auto Complete详解
2019/06/18 jQuery
vue3.0 自适应不同分辨率电脑的操作
2021/02/06 Vue.js
Python字符串格式化
2015/06/15 Python
Fiddler如何抓取手机APP数据包
2016/01/22 Python
python使用itchat库实现微信机器人(好友聊天、群聊天)
2018/01/04 Python
快速解决PyCharm无法引用matplotlib的问题
2018/05/24 Python
python实现搜索文本文件内容脚本
2018/06/22 Python
python获取引用对象的个数方式
2019/12/20 Python
Python中的__init__作用是什么
2020/06/09 Python
python实现数字炸弹游戏
2020/07/17 Python
伦敦最受欢迎的蛋糕店:Konditor & Cook
2019/11/01 全球购物
精伦电子Java笔试题
2013/01/16 面试题
党校学习思想汇报
2014/01/06 职场文书
建议书标准格式
2014/03/12 职场文书
学校运动会广播稿
2014/10/11 职场文书
安全生产工作汇报
2014/10/28 职场文书
考研英语辞职信
2015/05/13 职场文书
2015教师节通讯稿
2015/07/20 职场文书