HTML5实现烟花绽放(支持老版浏览器)

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

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

<!-- setup our canvas element -->  
<canvas id="canvas">Canvas is not supported in your browser.</canvas>  

css文件

body {  
    background: #000;  
    margin: 0;  
}  
canvas {  
    cursor: crosshair;  
    display: block;  
}  

接着是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上查看代码片派生到我的代码片
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/  
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating  
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel  
// MIT license  
   
(function() {  
    var lastTime = 0;  
    var vendors = ['ms', 'moz', 'webkit', 'o'];  
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {  
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];  
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']   
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];  
    }  
   
    if (!window.requestAnimationFrame)  
        window.requestAnimationFrame = function(callback, element) {  
            var currTime = new Date().getTime();  
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));  
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },   
              timeToCall);  
            lastTime = currTime + timeToCall;  
            return id;  
        };  
   
    if (!window.cancelAnimationFrame)  
        window.cancelAnimationFrame = function(id) {  
            clearTimeout(id);  
        };  
}());  

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

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
// create particle  
function Particle( x, y ) {  
    this.x = x;  
    this.y = y;  
    // track the past coordinates of each particle to create a trail effect, increase the coordinate count to create more prominent trails  
    this.coordinates = [];  
    this.coordinateCount = 5;  
    while( this.coordinateCount-- ) {  
        this.coordinates.push( [ this.x, this.y ] );  
    }  
    // set a random angle in all possible directions, in radians  
    this.angle = random( 0, Math.PI * 2 );  
    this.speed = random( 1, 10 );  
    // friction will slow the particle down  
    this.friction = 0.95;  
    // gravity will be applied and pull the particle down  
    this.gravity = 1;  
    // set the hue to a random number +-20 of the overall hue variable  
    this.hue = random( hue - 20, hue + 20 );  
    this.brightness = random( 50, 80 );  
    this.alpha = 1;  
    // set how fast the particle fades out  
    this.decay = random( 0.015, 0.03 );  
}  
  
// update particle  
Particle.prototype.update = function( index ) {  
    // remove last item in coordinates array  
    this.coordinates.pop();  
    // add current coordinates to the start of the array  
    this.coordinates.unshift( [ this.x, this.y ] );  
    // slow down the particle  
    this.speed *= this.friction;  
    // apply velocity  
    this.x += Math.cos( this.angle ) * this.speed;  
    this.y += Math.sin( this.angle ) * this.speed + this.gravity;  
    // fade out the particle  
    this.alpha -= this.decay;  
      
    // remove the particle once the alpha is low enough, based on the passed in index  
    if( this.alpha <= this.decay ) {  
        particles.splice( index, 1 );  
    }  
}  
  
// draw particle  
Particle.prototype.draw = function() {  
    ctx. beginPath();  
    // move to the last tracked coordinates in the set, then draw a line to the current x and y  
    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();  
}  

生成粒子的函数

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

烟花类

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
// create firework  
function Firework( sx, sy, tx, ty ) {  
    // actual coordinates  
    this.x = sx;  
    this.y = sy;  
    // starting coordinates  
    this.sx = sx;  
    this.sy = sy;  
    // target coordinates  
    this.tx = tx;  
    this.ty = ty;  
    // distance from starting point to target  
    this.distanceToTarget = calculateDistance( sx, sy, tx, ty );  
    this.distanceTraveled = 0;  
    // track the past coordinates of each firework to create a trail effect, increase the coordinate count to create more prominent trails  
    this.coordinates = [];  
    this.coordinateCount = 3;  
    // populate initial coordinate collection with the current coordinates  
    while( this.coordinateCount-- ) {  
        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 );  
    // circle target indicator radius  
    this.targetRadius = 1;  
}  
  
// update firework  
Firework.prototype.update = function( index ) {  
    // remove last item in coordinates array  
    this.coordinates.pop();  
    // add current coordinates to the start of the array  
    this.coordinates.unshift( [ this.x, this.y ] );  
      
    // cycle the circle target indicator radius  
    if( this.targetRadius < 8 ) {  
        this.targetRadius += 0.3;  
    } else {  
        this.targetRadius = 1;  
    }  
      
    // speed up the firework  
    this.speed *= this.acceleration;  
      
    // get the current velocities based on angle and speed  
    var vx = Math.cos( this.angle ) * this.speed,  
        vy = Math.sin( this.angle ) * this.speed;  
    // how far will the firework have traveled with velocities applied?  
    this.distanceTraveled = calculateDistance( this.sx, this.sy, this.x + vx, this.y + vy );  
      
    // if the distance traveled, including velocities, is greater than the initial distance to the target, then the target has been reached  
    if( this.distanceTraveled >= this.distanceToTarget ) {  
        createParticles( this.tx, this.ty );  
        // remove the firework, use the index passed into the update function to determine which to remove  
        fireworks.splice( index, 1 );  
    } else {  
        // target not reached, keep traveling  
        this.x += vx;  
        this.y += vy;  
    }  
}  
  
// draw firework  
Firework.prototype.draw = function() {  
    ctx.beginPath();  
    // move to the last tracked coordinate in the set, then draw a line to the current x and y  
    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();  
    // draw the target for this firework with a pulsing circle  
    ctx.arc( this.tx, this.ty, this.targetRadius, 0, Math.PI * 2 );  
    ctx.stroke();  
}  

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

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
// now we will setup our basic variables for the demo  
var canvas = document.getElementById( 'canvas' ),  
        ctx = canvas.getContext( '2d' ),  
        // full screen dimensions  
        cw = window.innerWidth,  
        ch = window.innerHeight,  
        // firework collection  
        fireworks = [],  
        // particle collection  
        particles = [],  
        // starting hue  
        hue = 120,  
        // when launching fireworks with a click, too many get launched at once without a limiter, one launch per 5 loop ticks  
        limiterTotal = 5,  
        limiterTick = 0,  
        // this will time the auto launches of fireworks, one launch per 80 loop ticks  
        timerTotal = 80,  
        timerTick = 0,  
        mousedown = false,  
        // mouse x coordinate,  
        mx,  
        // mouse y coordinate  
        my;  
          
// set canvas dimensions  
canvas.width = cw;  
canvas.height = ch;  
  
// now we are going to setup our function placeholders for the entire demo  
  
// get a random number within a range  
function random( min, max ) {  
    return Math.random() * ( max - min ) + min;  
}  
  
// calculate the distance between two points  
function calculateDistance( p1x, p1y, p2x, p2y ) {  
    var xDistance = p1x - p2x,  
            yDistance = p1y - p2y;  
    return Math.sqrt( Math.pow( xDistance, 2 ) + Math.pow( yDistance, 2 ) );  
}  

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

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
// main demo loop  
function loop() {  
    // this function will run endlessly with requestAnimationFrame  
    requestAnimFrame( loop );  
      
    // increase the hue to get different colored fireworks over time  
    hue += 0.5;  
      
    // normally, clearRect() would be used to clear the canvas  
    // we want to create a trailing effect though  
    // setting the composite operation to destination-out will allow us to clear the canvas at a specific opacity, rather than wiping it entirely  
    ctx.globalCompositeOperation = 'destination-out';  
    // decrease the alpha property to create more prominent trails  
    ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';  
    ctx.fillRect( 0, 0, cw, ch );  
    // change the composite operation back to our main mode  
    // lighter creates bright highlight points as the fireworks and particles overlap each other  
    ctx.globalCompositeOperation = 'lighter';  
      
    // loop over each firework, draw it, update it  
    var i = fireworks.length;  
    while( i-- ) {  
        fireworks[ i ].draw();  
        fireworks[ i ].update( i );  
    }  
      
    // loop over each particle, draw it, update it  
    var i = particles.length;  
    while( i-- ) {  
        particles[ i ].draw();  
        particles[ i ].update( i );  
    }  
      
    // launch fireworks automatically to random coordinates, when the mouse isn't down  
    if( timerTick >= timerTotal ) {  
        if( !mousedown ) {  
            // 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  
            fireworks.push( new Firework( cw / 2, ch, random( 0, cw ), random( 0, ch / 2 ) ) );  
            timerTick = 0;  
        }  
    } else {  
        timerTick++;  
    }  
      
    // limit the rate at which fireworks get launched when mouse is down  
    if( limiterTick >= limiterTotal ) {  
        if( mousedown ) {  
            // start the firework at the bottom middle of the screen, then set the current mouse coordinates as the target  
            fireworks.push( new Firework( cw / 2, ch, mx, my ) );  
            limiterTick = 0;  
        }  
    } else {  
        limiterTick++;  
    }  
}  

绑定鼠标事件

[javascript] view plaincopy在CODE上查看代码片派生到我的代码片
// mouse event bindings  
// update the mouse coordinates on mousemove  
canvas.addEventListener( 'mousemove', function( e ) {  
    mx = e.pageX - canvas.offsetLeft;  
    my = e.pageY - canvas.offsetTop;  
});  
  
// toggle mousedown state and prevent canvas from being selected  
canvas.addEventListener( 'mousedown', function( e ) {  
    e.preventDefault();  
    mousedown = true;  
});  
  
canvas.addEventListener( 'mouseup', function( e ) {  
    e.preventDefault();  
    mousedown = false;  
});  

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

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

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

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

转自:https://software.intel.com/zh-cn/blogs/2014/02/28/html5/?utm_campaign=CSDN&utm_source=intel.csdn.net&utm_medium=Link&utm_content=others-html5

posted @ 2014-04-13 19:21  瞭望者  阅读(931)  评论(0)    收藏  举报