炫酷HTML5前端設計源碼分析筆記——落焰
前幾天發現一個比較有意思的設計,讓人很容易聯想到岩漿,看下演示:
HTML5 Canvas炫酷的火焰風暴動畫DEMO演示
/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Vars=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/var c = document.createElement( canvas ), ctx = c.getContext( 2d ), w = c.width = 1600, h = c.height = 800, particles = [], particleCount = 1000, particlePath = 4, pillars = [], pillarCount = 110, hue = 0, hueRange = 60, hueChange = 1, gravity = 0.1, lineWidth = 1, lineCap = round, PI = Math.PI, TWO_PI = PI * 2;/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Utility=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/function rand( min, max ) { return Math.random() * ( max - min ) + min;}function distance( a, b ) { var dx = a.x - b.x, dy = a.y - b.y; return Math.sqrt( dx * dx + dy * dy );}/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Particle=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/function Particle( opt ) { this.path = []; this.reset();}Particle.prototype.reset = function() { this.radius = 1; this.x = rand( 0, w ); this.y = 0; this.vx = 0; this.vy = 0; this.hit = 0; this.path.length = 0;};Particle.prototype.step = function() { this.hit = 0; this.path.unshift( [ this.x, this.y ] ); if( this.path.length > particlePath ) { this.path.pop(); } this.vy += gravity; this.x += this.vx; this.y += this.vy; if( this.y > h + 10 ) { this.reset(); } var i = pillarCount; while( i-- ) { var pillar = pillars[ i ]; if( distance( this, pillar ) < this.radius + pillar.renderRadius ) { this.vx = 0; this.vy = 0; this.vx += -( pillar.x - this.x ) * rand( 0.01, 0.03 ); this.vy += -( pillar.y - this.y ) * rand( 0.01, 0.03 ); pillar.radius -= 0.1; this.hit = 1; } }};Particle.prototype.draw = function() { ctx.beginPath(); ctx.moveTo( this.x, ~~this.y ); for( var i = 0, length = this.path.length; i < length; i++ ) { var point = this.path[ i ]; ctx.lineTo( point[ 0 ], ~~point[ 1 ] ); } ctx.strokeStyle = hsla( + rand( hue + ( this.x / 3 ), hue + ( this.x / 3 ) + hueRange ) + , 50%, 30%, 0.3); ctx.stroke(); if( this.hit ) { ctx.beginPath(); ctx.arc( this.x, this.y , rand( 1, 25 ), 0, TWO_PI ); ctx.fillStyle = hsla( + rand( hue + ( this.x / 3 ), hue + ( this.x / 3 ) + hueRange ) + , 80%, 15%, 0.1) ctx.fill(); }};/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Pillar=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/function Pillar() { this.reset();}Pillar.prototype.reset = function(){ this.radius = rand( 50, 200 ); this.renderRadius = 0; this.x = rand( 0, w ); this.y = rand( h / 2 - h / 4, h ); this.active = 0;};Pillar.prototype.step = function() { if( this.active ) { if( this.radius <= 1 ) { this.reset(); } else { this.renderRadius = this.radius; } } else { if( this.renderRadius < this.radius ) { this.renderRadius += 0.5; } else { this.active = 1; } }};Pillar.prototype.draw = function() { ctx.beginPath(); ctx.arc( this.x, this.y, this.renderRadius, 0, TWO_PI, false ); ctx.fill();};/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Init=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/function init() { ctx.lineWidth = lineWidth; ctx.lineCap = lineCap; var i = pillarCount; while( i-- ){ pillars.push( new Pillar() ); } document.body.appendChild( c ); loop();}/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Step=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/function step() { hue += hueChange; if( particles.length < particleCount ) { particles.push( new Particle() ); } var i = particles.length; while( i-- ) { particles[ i ].step(); } i = pillarCount; while( i-- ) { pillars[ i ].step(); }}/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Draw=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/function draw() { ctx.fillStyle = hsla(0, 0%, 0%, 0.1); ctx.fillRect( 0, 0, w, h ); ctx.globalCompositeOperation = lighter; var i = particles.length; while( i-- ) { particles[ i ].draw(); } ctx.globalCompositeOperation = source-over; i = pillarCount; ctx.fillStyle = rgba(20, 20, 20, 0.3); while( i-- ) { pillars[ i ].draw(); }}/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Loop=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/function loop() { requestAnimationFrame( loop ); step(); draw();}/*/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/Blast Off=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=/=*/init();
如你所見,代碼很簡短,去掉注釋只有二百行左右,我們現在就來看下這個炫麗的效果是怎麼實現的。
function Particle( opt ) { this.path = []; this.reset();}Particle.prototype.reset = function() { this.radius = 1; this.x = rand( 0, w ); this.y = 0; this.vx = 0; this.vy = 0; this.hit = 0; this.path.length = 0;};function Pillar() { this.reset();}Pillar.prototype.reset = function(){ this.radius = rand( 50, 200 ); this.renderRadius = 0; this.x = rand( 0, w ); this.y = rand( h / 2 - h / 4, h ); this.active = 0;};
在初始化過程中,定義了兩個主要對象,一個是粒子(Particle),一個是球體(Pillar)。
對象屬性也很直觀,就不細說了。
初始化函數中,按設置生成相應數量的對象後,就進入了loop();功能就是先更新屬性,後畫圖。
function step() { hue += hueChange; if( particles.length < particleCount ) { particles.push( new Particle() ); } var i = particles.length; while( i-- ) { particles[ i ].step(); } i = pillarCount; while( i-- ) { pillars[ i ].step(); }}
在更新屬性的主要函數里更新每一個粒子與每一個球體的屬性
Particle.prototype.step = function() { this.hit = 0; this.path.unshift( [ this.x, this.y ] );//1 if( this.path.length > particlePath ) { this.path.pop(); } this.vy += gravity;//2 this.x += this.vx; this.y += this.vy; if( this.y > h + 10 ) { this.reset(); } var i = pillarCount;//3 while( i-- ) { var pillar = pillars[ i ]; if( distance( this, pillar ) < this.radius + pillar.renderRadius ) { this.vx = 0; this.vy = 0; this.vx += -( pillar.x - this.x ) * rand( 0.01, 0.03 ); this.vy += -( pillar.y - this.y ) * rand( 0.01, 0.03 ); pillar.radius -= 0.1; this.hit = 1; } }};
更新每一個粒子的函數所做的事情也非常簡單:
1.記錄上一時刻的粒子坐標(記錄路徑)。
2.根據規則(重力),更正粒子坐標,並檢驗是否超出窗口邊界,如果超出則重置粒子屬性。
3.(碰撞檢測)判斷粒子與球體圓心的距離,如果距離小於兩物體半徑和,則視為碰撞,改變粒子的位移變動量(在下一時刻累加),並把球體的半徑減小相應數目。注意:這裡把狀態hit標記為true
Pillar.prototype.step = function() { if( this.active ) { if( this.radius <= 1 ) { this.reset(); } else { this.renderRadius = this.radius; } } else { if( this.renderRadius < this.radius ) { this.renderRadius += 0.5; } else { this.active = 1; } }};
球體的更新函數所做的事是:如果沒有現在的半徑還沒到隨機初始化的最大半徑,則半徑以0.5步長增長;若達到了,則保持半徑與粒子互動;半徑小於1則重置球體。
更新部分就完了,下面看繪畫部分:
function draw() { ctx.fillStyle = hsla(0, 0%, 0%, 0.1); ctx.fillRect( 0, 0, w, h ); ctx.globalCompositeOperation = lighter; var i = particles.length; while( i-- ) { particles[ i ].draw(); } ctx.globalCompositeOperation = source-over; i = pillarCount; ctx.fillStyle = rgba(20, 20, 20, 0.3); while( i-- ) { pillars[ i ].draw(); }}
工整的格式,與更新一樣,就是繪製每一個粒子與每一個球體。
Particle.prototype.draw = function() { ctx.beginPath(); ctx.moveTo( this.x, ~~this.y ); for( var i = 0, length = this.path.length; i < length; i++ ) { var point = this.path[ i ]; ctx.lineTo( point[ 0 ], ~~point[ 1 ] ); } ctx.strokeStyle = hsla( + rand( hue + ( this.x / 3 ), hue + ( this.x / 3 ) + hueRange ) + , 50%, 30%, 0.3); ctx.stroke(); if( this.hit ) { ctx.beginPath(); ctx.arc( this.x, this.y , rand( 1, 25 ), 0, TWO_PI ); ctx.fillStyle = hsla( + rand( hue + ( this.x / 3 ), hue + ( this.x / 3 ) + hueRange ) + , 80%, 15%, 0.1) ctx.fill(); }};
粒子繪製函數的作用是:沿著粒子的路徑,顯示彗尾,這樣設置的好處是看著更加自然,粒子速度越快,彗尾就會越長;隨機顏色;如果粒子擊中球體,則會顯示一個固定範圍內半徑隨機、顏色與粒子相同的圓(產生擊中的動態效果)。
Pillar.prototype.draw = function() { ctx.beginPath(); ctx.arc( this.x, this.y, this.renderRadius, 0, TWO_PI, false ); ctx.fill();};
球體繪製函數就是繪製這個球體。。。。。
現在我們再回過頭看一下她的整體流程,就會發現這麼炫麗的效果的原理,其實就是一些十分簡單的物體與動作的組合!所以,道理是:
任何幻術的目的是讓觀看者信以為真,我們不需要完全模擬真實的物理過程,只要看起來真假難辨,那麼這個作品就成功了!!!
推薦閱讀: