html5 实现烟花绽放

今天来看一个利用canvas+javascript实现的烟花绽放效果,大家可以到我的codepen看看DEMO,在线观看或是下载研究,悉听尊便。

首先来看看html,里面就放了一个canvas对象。

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. <!-- setup our canvas element -->  
  2. <canvas id="canvas">Canvas is not supported in your browser.</canvas>  

css文件

[css] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. body {  
  2.     background#000;  
  3.     margin0;  
  4. }  
  5. canvas {  
  6.     cursorcrosshair;  
  7.     displayblock;  
  8. }  

接着是JS,首先使用“RequestAnimationFrame”实现动画,可以有效解决setTimeout和setInterval的浪费CPU问题。关于RequestAnimationFrame方法的特性和优势,大家可以参考以下几篇文章。

CSS-Tricks上的《Using requestAnimationFrame

Treehouse Blog上的《Efficient Animations with requestAnimationFrame

W3c的候选推荐标准《Timing control for script-based animations

以及Erik Möller提供的polyfill(在旧版浏览器上模拟标准 API 的 JavaScript 补充)。

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // http://paulirish.com/2011/requestanimationframe-for-smart-animating/  
  2. // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating  
  3. // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel  
  4. // MIT license  
  5.    
  6. (function() {  
  7.     var lastTime = 0;  
  8.     var vendors = ['ms''moz''webkit''o'];  
  9.     for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {  
  10.         window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];  
  11.         window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']   
  12.                                    || window[vendors[x]+'CancelRequestAnimationFrame'];  
  13.     }  
  14.    
  15.     if (!window.requestAnimationFrame)  
  16.         window.requestAnimationFrame = function(callback, element) {  
  17.             var currTime = new Date().getTime();  
  18.             var timeToCall = Math.max(0, 16 - (currTime - lastTime));  
  19.             var id = window.setTimeout(function() { callback(currTime + timeToCall); },   
  20.               timeToCall);  
  21.             lastTime = currTime + timeToCall;  
  22.             return id;  
  23.         };  
  24.    
  25.     if (!window.cancelAnimationFrame)  
  26.         window.cancelAnimationFrame = function(id) {  
  27.             clearTimeout(id);  
  28.         };  
  29. }());  

本案例需要两个类烟花沫粒子和烟花。首先来看烟花沫类

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // create particle  
  2. function Particle( x, y ) {  
  3.     this.x = x;  
  4.     this.y = y;  
  5.     // track the past coordinates of each particle to create a trail effect, increase the coordinate count to create more prominent trails  
  6.     this.coordinates = [];  
  7.     this.coordinateCount = 5;  
  8.     whilethis.coordinateCount-- ) {  
  9.         this.coordinates.push( [ this.x, this.y ] );  
  10.     }  
  11.     // set a random angle in all possible directions, in radians  
  12.     this.angle = random( 0, Math.PI * 2 );  
  13.     this.speed = random( 1, 10 );  
  14.     // friction will slow the particle down  
  15.     this.friction = 0.95;  
  16.     // gravity will be applied and pull the particle down  
  17.     this.gravity = 1;  
  18.     // set the hue to a random number +-20 of the overall hue variable  
  19.     this.hue = random( hue - 20, hue + 20 );  
  20.     this.brightness = random( 50, 80 );  
  21.     this.alpha = 1;  
  22.     // set how fast the particle fades out  
  23.     this.decay = random( 0.015, 0.03 );  
  24. }  
  25.   
  26. // update particle  
  27. Particle.prototype.update = function( index ) {  
  28.     // remove last item in coordinates array  
  29.     this.coordinates.pop();  
  30.     // add current coordinates to the start of the array  
  31.     this.coordinates.unshift( [ this.x, this.y ] );  
  32.     // slow down the particle  
  33.     this.speed *= this.friction;  
  34.     // apply velocity  
  35.     this.x += Math.cos( this.angle ) * this.speed;  
  36.     this.y += Math.sin( this.angle ) * this.speed + this.gravity;  
  37.     // fade out the particle  
  38.     this.alpha -= this.decay;  
  39.       
  40.     // remove the particle once the alpha is low enough, based on the passed in index  
  41.     ifthis.alpha <= this.decay ) {  
  42.         particles.splice( index, 1 );  
  43.     }  
  44. }  
  45.   
  46. // draw particle  
  47. Particle.prototype.draw = function() {  
  48.     ctx. beginPath();  
  49.     // move to the last tracked coordinates in the set, then draw a line to the current x and y  
  50.     ctx.moveTo( this.coordinates[ this.coordinates.length - 1 ][ 0 ], this.coordinates[ this.coordinates.length - 1 ][ 1 ] );  
  51.     ctx.lineTo( this.x, this.y );  
  52.     ctx.strokeStyle = 'hsla(' + this.hue + ', 100%, ' + this.brightness + '%, ' + this.alpha + ')';  
  53.     ctx.stroke();  
  54. }  

生成粒子的函数

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // create particle group/explosion  
  2. function createParticles( x, y ) {  
  3.     // increase the particle count for a bigger explosion, beware of the canvas performance hit with the increased particles though  
  4.     var particleCount = 30;  
  5.     while( particleCount-- ) {  
  6.         particles.push( new Particle( x, y ) );  
  7.     }  
  8. }  

烟花类

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // create firework  
  2. function Firework( sx, sy, tx, ty ) {  
  3.     // actual coordinates  
  4.     this.x = sx;  
  5.     this.y = sy;  
  6.     // starting coordinates  
  7.     this.sx = sx;  
  8.     this.sy = sy;  
  9.     // target coordinates  
  10.     this.tx = tx;  
  11.     this.ty = ty;  
  12.     // distance from starting point to target  
  13.     this.distanceToTarget = calculateDistance( sx, sy, tx, ty );  
  14.     this.distanceTraveled = 0;  
  15.     // track the past coordinates of each firework to create a trail effect, increase the coordinate count to create more prominent trails  
  16.     this.coordinates = [];  
  17.     this.coordinateCount = 3;  
  18.     // populate initial coordinate collection with the current coordinates  
  19.     whilethis.coordinateCount-- ) {  
  20.         this.coordinates.push( [ this.x, this.y ] );  
  21.     }  
  22.     this.angle = Math.atan2( ty - sy, tx - sx );  
  23.     this.speed = 2;  
  24.     this.acceleration = 1.05;  
  25.     this.brightness = random( 50, 70 );  
  26.     // circle target indicator radius  
  27.     this.targetRadius = 1;  
  28. }  
  29.   
  30. // update firework  
  31. Firework.prototype.update = function( index ) {  
  32.     // remove last item in coordinates array  
  33.     this.coordinates.pop();  
  34.     // add current coordinates to the start of the array  
  35.     this.coordinates.unshift( [ this.x, this.y ] );  
  36.       
  37.     // cycle the circle target indicator radius  
  38.     ifthis.targetRadius < 8 ) {  
  39.         this.targetRadius += 0.3;  
  40.     } else {  
  41.         this.targetRadius = 1;  
  42.     }  
  43.       
  44.     // speed up the firework  
  45.     this.speed *= this.acceleration;  
  46.       
  47.     // get the current velocities based on angle and speed  
  48.     var vx = Math.cos( this.angle ) * this.speed,  
  49.         vy = Math.sin( this.angle ) * this.speed;  
  50.     // how far will the firework have traveled with velocities applied?  
  51.     this.distanceTraveled = calculateDistance( this.sx, this.sy, this.x + vx, this.y + vy );  
  52.       
  53.     // if the distance traveled, including velocities, is greater than the initial distance to the target, then the target has been reached  
  54.     ifthis.distanceTraveled >= this.distanceToTarget ) {  
  55.         createParticles( this.tx, this.ty );  
  56.         // remove the firework, use the index passed into the update function to determine which to remove  
  57.         fireworks.splice( index, 1 );  
  58.     } else {  
  59.         // target not reached, keep traveling  
  60.         this.x += vx;  
  61.         this.y += vy;  
  62.     }  
  63. }  
  64.   
  65. // draw firework  
  66. Firework.prototype.draw = function() {  
  67.     ctx.beginPath();  
  68.     // move to the last tracked coordinate in the set, then draw a line to the current x and y  
  69.     ctx.moveTo( this.coordinates[ this.coordinates.length - 1][ 0 ], this.coordinates[ this.coordinates.length - 1][ 1 ] );  
  70.     ctx.lineTo( this.x, this.y );  
  71.     ctx.strokeStyle = 'hsl(' + hue + ', 100%, ' + this.brightness + '%)';  
  72.     ctx.stroke();  
  73.       
  74.     ctx.beginPath();  
  75.     // draw the target for this firework with a pulsing circle  
  76.     ctx.arc( this.tx, this.ty, this.targetRadius, 0, Math.PI * 2 );  
  77.     ctx.stroke();  
  78. }  

定义好了主要的类,我们来设置一些常见的变量和函数。

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // now we will setup our basic variables for the demo  
  2. var canvas = document.getElementById( 'canvas' ),  
  3.         ctx = canvas.getContext( '2d' ),  
  4.         // full screen dimensions  
  5.         cw = window.innerWidth,  
  6.         ch = window.innerHeight,  
  7.         // firework collection  
  8.         fireworks = [],  
  9.         // particle collection  
  10.         particles = [],  
  11.         // starting hue  
  12.         hue = 120,  
  13.         // when launching fireworks with a click, too many get launched at once without a limiter, one launch per 5 loop ticks  
  14.         limiterTotal = 5,  
  15.         limiterTick = 0,  
  16.         // this will time the auto launches of fireworks, one launch per 80 loop ticks  
  17.         timerTotal = 80,  
  18.         timerTick = 0,  
  19.         mousedown = false,  
  20.         // mouse x coordinate,  
  21.         mx,  
  22.         // mouse y coordinate  
  23.         my;  
  24.           
  25. // set canvas dimensions  
  26. canvas.width = cw;  
  27. canvas.height = ch;  
  28.   
  29. // now we are going to setup our function placeholders for the entire demo  
  30.   
  31. // get a random number within a range  
  32. function random( min, max ) {  
  33.     return Math.random() * ( max - min ) + min;  
  34. }  
  35.   
  36. // calculate the distance between two points  
  37. function calculateDistance( p1x, p1y, p2x, p2y ) {  
  38.     var xDistance = p1x - p2x,  
  39.             yDistance = p1y - p2y;  
  40.     return Math.sqrt( Math.pow( xDistance, 2 ) + Math.pow( yDistance, 2 ) );  
  41. }  

不断释放烟花的函数,需要利用RequestAnimationFrame不断地执行函数。

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // main demo loop  
  2. function loop() {  
  3.     // this function will run endlessly with requestAnimationFrame  
  4.     requestAnimFrame( loop );  
  5.       
  6.     // increase the hue to get different colored fireworks over time  
  7.     hue += 0.5;  
  8.       
  9.     // normally, clearRect() would be used to clear the canvas  
  10.     // we want to create a trailing effect though  
  11.     // setting the composite operation to destination-out will allow us to clear the canvas at a specific opacity, rather than wiping it entirely  
  12.     ctx.globalCompositeOperation = 'destination-out';  
  13.     // decrease the alpha property to create more prominent trails  
  14.     ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';  
  15.     ctx.fillRect( 0, 0, cw, ch );  
  16.     // change the composite operation back to our main mode  
  17.     // lighter creates bright highlight points as the fireworks and particles overlap each other  
  18.     ctx.globalCompositeOperation = 'lighter';  
  19.       
  20.     // loop over each firework, draw it, update it  
  21.     var i = fireworks.length;  
  22.     while( i-- ) {  
  23.         fireworks[ i ].draw();  
  24.         fireworks[ i ].update( i );  
  25.     }  
  26.       
  27.     // loop over each particle, draw it, update it  
  28.     var i = particles.length;  
  29.     while( i-- ) {  
  30.         particles[ i ].draw();  
  31.         particles[ i ].update( i );  
  32.     }  
  33.       
  34.     // launch fireworks automatically to random coordinates, when the mouse isn't down  
  35.     if( timerTick >= timerTotal ) {  
  36.         if( !mousedown ) {  
  37.             // start the firework at the bottom middle of the screen, then set the random target coordinates, the random y coordinates will be set within the range of the top half of the screen  
  38.             fireworks.push( new Firework( cw / 2, ch, random( 0, cw ), random( 0, ch / 2 ) ) );  
  39.             timerTick = 0;  
  40.         }  
  41.     } else {  
  42.         timerTick++;  
  43.     }  
  44.       
  45.     // limit the rate at which fireworks get launched when mouse is down  
  46.     if( limiterTick >= limiterTotal ) {  
  47.         if( mousedown ) {  
  48.             // start the firework at the bottom middle of the screen, then set the current mouse coordinates as the target  
  49.             fireworks.push( new Firework( cw / 2, ch, mx, my ) );  
  50.             limiterTick = 0;  
  51.         }  
  52.     } else {  
  53.         limiterTick++;  
  54.     }  
  55. }  

绑定鼠标事件

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // mouse event bindings  
  2. // update the mouse coordinates on mousemove  
  3. canvas.addEventListener( 'mousemove'function( e ) {  
  4.     mx = e.pageX - canvas.offsetLeft;  
  5.     my = e.pageY - canvas.offsetTop;  
  6. });  
  7.   
  8. // toggle mousedown state and prevent canvas from being selected  
  9. canvas.addEventListener( 'mousedown'function( e ) {  
  10.     e.preventDefault();  
  11.     mousedown = true;  
  12. });  
  13.   
  14. canvas.addEventListener( 'mouseup'function( e ) {  
  15.     e.preventDefault();  
  16.     mousedown = false;  
  17. });  

在窗口加载完毕,单击鼠标前,我们也需要一些烟花

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. // once the window loads, we are ready for some fireworks!  
  2. window.onload = loop;  

完毕。

---------------------------------------------------------------

前端开发whqet,关注web前端开发技术,分享网页相关资源。
---------------------------------------------------------------

Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.
Categorías: