超炫的HTML5粒子效果進度條 VS 如何規範而優雅地碼磚

来源:http://www.cnblogs.com/qieguo/archive/2016/04/27/5438380.html
-Advertisement-
Play Games

最近瞎逛的時候發現了一個超炫的粒子進度效果,有多炫呢?請擦亮眼鏡! // <![CDATA[ /* */ /* Light Loader /* */ var lightLoader = function(c, cw, ch){ var _this = this; this.c = c; this.c ...


最近瞎逛的時候發現了一個超炫的粒子進度效果,有多炫呢?請擦亮眼鏡!

 

粗略一看真的被驚艷到了,而且很實用啊有木有!這是 Jack Rugile 寫的一個小效果,源碼當然是有的。聰慧如你,肯定覺得這個東西so easy 要看啥源碼,給我3分鐘我就寫出來了吧。所以你的思路可能是:

1)進度條的實現沒什麼好說的,簡單的一個 fillRect(0,0,long,20),long和20是分別進度條的長寬。然後每幀動畫調用前將畫布清除clearRect(0,0,canvas.width,canvas.height)。做出來應該是這樣的(點擊啟動/暫停動畫):

 

2)進度條色彩的變化。這個也簡單,顏色漸變嘛,fillStyle = createLinearGradient() 就行了吧。不對哦,是顏色隨時間變化,每一幀內的進度條顏色一樣的哦。理所當然就能搞出一句:fillStyle = rgba(f(t),f(t),f(t),1),f(t)是隨時間變化的函數。然而,這些只知道rgba的哥們,發現怎麼調也調不出這樣的漸變效果,rgb變化哪一個都會造成顏色明暗變化,卡殼了吧,這裡估計要卡掉5%的人。要保持亮度不發生變化,這裡要用到hsla這種顏色格式,就是妹子們自拍修圖時常用的色調/飽和度/亮度/透明度。對照進度條的效果,明顯我們只要改色調就OK了。

ctx.fillStyle = 'hsla('+(hue++)+', 100%, 40%, 1)'; 

結果可能是這樣的(點擊啟動/暫停動畫):

 

3)接下來進入正題,要做粒子效果了。粒子很多,觀察力不好或者沒掌握方法的同學這裡就要歇菜啦(此處應有博主爽朗的笑聲,哈哈哈~)。對於元素數量巨大的效果,我們應該儘可能縮小觀察範圍,只觀察一個或者一組元素,找出單體的規律。多看幾次,就能發現單個粒子是先向上運動一陣子然後掉下去,單個粒子的x軸應該是不變的。對於粒子集合來說,每個粒子的x坐標遞增,就能產生我們需要的效果了。這裡推薦同學們去看一下MDN的常式,超好玩的ball(好玩、ball?嘿嘿~):https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Advanced_animations

這裡我們每幀只添加一個粒子:

var raf = null,
    c = document.createElement('canvas'),
    parent = document.getElementById('canvas-wrapper-test3');
    c.width = 400;
    c.height = 100;            
    c.id = 'canvas-test3';
    parent.appendChild(c);
var ctx = c.getContext('2d'),
    hue = 0,    //色調
    vy = -2,     //y軸速度
    par = [],        //粒子數組
    x = 0,            //進度條當前位置
    draw = function () {
        var color;
    ctx.clearRect(0,0,c.width,c.height);
        x += 3;   //進度條速度為每幀3個像素
    hue = (x > 310) ? 0 : hue;  
        //顏色漸變為每幀1色調  
      color = 'hsla('+(hue++)+', 100%, 40%, 1)' ; 
        par.push({        //用數組模擬隊列
              px: x + 40,
          py: 50,
          pvy: vy,
          pcolor: 'hsla('+(hue+30)+', 100%, 70%, 1)',
      });        
      x = (x > 310) ? 0 : x;  //進度條到右側後返回
        ctx.fillStyle = color;
      ctx.fillRect(45, 40, x, 20);
        var n = par.length;                        
      while(n--){
             //切記要隨機差異化粒子y軸速度,否則就變成一根拋物線了
         par[n].pvy += (Math.random()+0.1)/5;  
           par[n].py += par[n].pvy;
         if (par[n].py > c.height ) {
              par.splice(n, 1);    //掉到畫布之外了,清除該粒子
              continue;
         }
         ctx.fillStyle = par[n].pcolor;
         ctx.fillRect(par[n].px, par[n].py, 5, 5);
      }
        raf = window.requestAnimationFrame(draw);
    };
raf = window.requestAnimationFrame(draw);

雖然簡單,但效果還是出來了(點擊啟動/暫停動畫):

 

至此,這個動畫效果基本完成了,後續要做的就是優化了:

1)增加粒子數量,現在我們每幀要push多個粒子進去,這樣數量上就上來了。

2)應該直接調用fillRect繪製小矩形代替圓形,些筒子可能會真的用arc畫一個粒子,囧。。。這裡稍微提點常識,電腦繪圖中所有曲線都是由直線構成的,要畫一個圓就相當於調用了相當多次的畫線功能,性能消耗非常大。在粒子這麼小的情況下,是圓是方只有瞎子才能分得清了,所以我們應該直接調用fillRect繪製小矩形代替圓形。這個也是canvas繪圖裡面常用的優化方法哦~

3)增加隨機化效果。現在xy起始坐標都跟進度條緊密聯繫在一起。我們每次生成幾個粒子的話,粒子初始坐標應該在一定範圍浮動,另外粒子的大小、顏色也應該要在小範圍內隨機化。顏色相對進度條顏色有一定滯後的話,效果會更加自然。

4)上面說到x方向不動,但是如果x方向增加一點抖動效果的話會更自然生動。

5)畫布顏色混合選項設置線性疊加:globalCompositeOperation = 'lighter',這樣在粒子重疊的時候顏色會有疊加的效果。這個是在源碼上看到的,大牛就是細節會做得比別人好的家伙!關於這個屬性的具體解釋可以看看這位"大白鯊"的實驗,中文的!http://www.cnblogs.com/jenry/archive/2012/02/11/2347012.html

 

總結一下:想要實現一個效果,首先我們要簡化模型,可以分成色彩的變化、位置的變化、大小的變化等,還有就是將某個因數獨立出來看,通過各種抽繭剝絲的手法去找到效果後面的數學模型,然後編程去實現它。藝術總是源於生活,所以在做時候應該好好考慮是否應該加入慣性、彈性、重力這些效果,這些物理特性反映到效果中的話,會更加自然逼真。

都總結了,那完事了?

NO!NO!NO!

接下來才是我想要說的重點!上面的代碼效果優化之後,老大看到效果覺得還不錯哦,加到新項目去吧。。。然後就是啪啦啪啦ctrlC ctrlV?好吧,你也猜到了我要說什麼,對的,復用和封裝。

先看人家的源碼,貌似這哥們連停止動畫都沒寫呢,就一個無限迴圈。。。

  1 var lightLoader = function(c, cw, ch){
  2 
  3     var that = this;
  4     this.c = c;
  5     this.ctx = c.getContext('2d');
  6     this.cw = cw;
  7     this.ch = ch;            
  8     this.raf = null;
  9     
 10     this.loaded = 0;
 11     this.loaderSpeed = .6;
 12     this.loaderWidth = cw * 0.8;
 13     this.loaderHeight = 20;
 14     this.loader = {
 15         x: (this.cw/2) - (this.loaderWidth/2),
 16         y: (this.ch/2) - (this.loaderHeight/2)
 17     };
 18     this.particles = [];
 19     this.particleLift = 220;
 20     this.hueStart = 0
 21     this.hueEnd = 120;
 22     this.hue = 0;
 23     this.gravity = .15;
 24     this.particleRate = 4;    
 25                     
 26     /*========================================================*/    
 27     /* Initialize
 28     /*========================================================*/
 29     this.init = function(){
 30         this.loaded = 0;
 31         this.particles = [];
 32         this.loop();
 33     };
 34     
 35     /*========================================================*/    
 36     /* Utility Functions
 37     /*========================================================*/                
 38     this.rand = function(rMi, rMa){return ~~((Math.random()*(rMa-rMi+1))+rMi);};
 39     this.hitTest = function(x1, y1, w1, h1, x2, y2, w2, h2){return !(x1 + w1 < x2 || x2 + w2 < x1 || y1 + h1 < y2 || y2 + h2 < y1);};
 40     
 41     /*========================================================*/    
 42     /* Update Loader
 43     /*========================================================*/
 44     this.updateLoader = function(){
 45         if(this.loaded < 100){
 46             this.loaded += this.loaderSpeed;
 47         } else {
 48             this.loaded = 0;
 49         }
 50     };
 51     
 52     /*========================================================*/    
 53     /* Render Loader
 54     /*========================================================*/
 55     this.renderLoader = function(){
 56         this.ctx.fillStyle = '#000';
 57         this.ctx.fillRect(this.loader.x, this.loader.y, this.loaderWidth, this.loaderHeight);
 58         
 59         this.hue = this.hueStart + (this.loaded/100)*(this.hueEnd - this.hueStart);
 60         
 61         var newWidth = (this.loaded/100)*this.loaderWidth;
 62         this.ctx.fillStyle = 'hsla('+this.hue+', 100%, 40%, 1)';
 63         this.ctx.fillRect(this.loader.x, this.loader.y, newWidth, this.loaderHeight);
 64         
 65         this.ctx.fillStyle = '#222';
 66         this.ctx.fillRect(this.loader.x, this.loader.y, newWidth, this.loaderHeight/2);
 67     };    
 68     
 69     /*========================================================*/    
 70     /* Particles
 71     /*========================================================*/
 72     this.Particle = function(){                    
 73         this.x = that.loader.x + ((that.loaded/100)*that.loaderWidth) - that.rand(0, 1);
 74         this.y = that.ch/2 + that.rand(0,that.loaderHeight)-that.loaderHeight/2;
 75         this.vx = (that.rand(0,4)-2)/100;
 76         this.vy = (that.rand(0,that.particleLift)-that.particleLift*2)/100;
 77         this.width = that.rand(2,6)/2;
 78         this.height = that.rand(2,6)/2;
 79         this.hue = that.hue;
 80     };
 81     
 82     this.Particle.prototype.update = function(i){
 83         this.vx += (that.rand(0,6)-3)/100; 
 84         this.vy += that.gravity;
 85         this.x += this.vx;
 86         this.y += this.vy;
 87         
 88         if(this.y > that.ch){
 89             that.particles.splice(i, 1);
 90         }                    
 91     };
 92     
 93     this.Particle.prototype.render = function(){
 94         that.ctx.fillStyle = 'hsla('+this.hue+', 100%, '+that.rand(50,70)+'%, '+that.rand(20,100)/100+')';
 95         that.ctx.fillRect(this.x, this.y, this.width, this.height);
 96     };
 97     
 98     this.createParticles = function(){
 99         var i = this.particleRate;
100         while(i--){
101             this.particles.push(new this.Particle());
102         };
103     };
104                     
105     this.updateParticles = function(){                    
106         var i = this.particles.length;                        
107         while(i--){
108             var p = this.particles[i];
109             p.update(i);                                            
110         };                        
111     };
112     
113     this.renderParticles = function(){
114         var i = this.particles.length;                        
115         while(i--){
116             var p = this.particles[i];
117             p.render();                                            
118         };                    
119     };
120     
121 
122     /*========================================================*/    
123     /* Clear Canvas
124     /*========================================================*/
125     this.clearCanvas = function(){
126         this.ctx.globalCompositeOperation = 'source-over';
127         this.ctx.clearRect(0,0,this.cw,this.ch);                    
128         this.ctx.globalCompositeOperation = 'lighter';
129     };
130     
131     /*========================================================*/    
132     /* Animation Loop
133     /*========================================================*/
134     this.loop = function(){
135         var loopIt = function(){
136             that.raf =  requestAnimationFrame(loopIt);
137             that.clearCanvas();
138             
139             that.createParticles();
140             
141             that.updateLoader();
142             that.updateParticles();
143             
144             that.renderLoader();
145             that.renderParticles();
146             
147         };
148         loopIt();                    
149     };
150     
151     
152     this.stop = function(){
153         this.ctx.globalCompositeOperation = 'source-over';
154         this.ctx.clearRect(0,0,this.cw,this.ch);
155         window.cancelAnimationFrame(this.raf);
156     }
157 
158 };
159 
160 
161 /*========================================================*/    
162 /* Setup requestAnimationFrame when it is unavailable.
163 /*========================================================*/
164 var setupRAF = function(){
165     var lastTime = 0;
166     var vendors = ['ms', 'moz', 'webkit', 'o'];
167     for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x){
168         window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
169         window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
170     };
171     
172     if(!window.requestAnimationFrame){
173         window.requestAnimationFrame = function(callback, element){
174             var currTime = new Date().getTime();
175             var timeToCall = Math.max(0, 16 - (currTime - lastTime));
176             var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
177             lastTime = currTime + timeToCall;
178             return id;
179         };
180     };
181     
182     if (!window.cancelAnimationFrame){
183         window.cancelAnimationFrame = function(id){
184             clearTimeout(id);
185         };
186     };
187 };
View Code

我在源碼基礎上加了個stop,初始化的時候清除了進度條位置和粒子位置,改動就這兩點。後續引用這個組件非常簡單:

var c = document.createElement('canvas');
c.width = 400;
c.height = 100;            
c.id = 'canvas-test1';
parent.appendChild(c);  //在需要的位置加入canvas元素
loader = new lightLoader(c,c.width,c.height);
setupRAF();        //不支持requestAnimationFrame瀏覽器的替代方案
loader.init();

----- 讀源碼 -----

這個源碼寫的也比較規範,結構清晰、組件和DOM分離得很好,是個學習的好題材!下麵說說我對源碼的理解,菜鳥一枚,有錯務必指出!

(一) 構造函數的形參

像進度條這樣的小組件,我們應該儘量將其封裝到一個全局變數中,如:var lightLoader = function(e) { }; 。源碼中傳入的參數是一個canvas和寬高,但是假如我們要設置進度條的屬性的時候,就必須到源碼裡面去改動了,這樣的話可復用性就打了個打折扣。還好,與進度條相關的屬性都被封裝到了全局變數的屬性中,要改動的話實例化後直接改lightLoade.屬性也可以使用。

如果要增加組件的自由度,可以使用一個對象作為形參:var lightLoader = function(opt) { };  

設置傳入一個對象的話,後續要對這個組件進行擴展或者改動的時候,那對象參數的便利性就體現得淋漓盡致了。

比如我要擴展一個進度條的寬度:this.loaderHeight = opt.loaderHeight ? opt.loaderHeight : 20; 就完事了(實參的類型和值的安全性暫不討論哈!)。原來的var lightLoader = function(c, cw, ch){} 如果要擴展一個進度條的寬度,想當然地我們可以寫出 var lightLoader = function(c, cw, ch, lw) { this.loaderHeight = lw ? lw : 20 },但是麻煩的是,當我們new lightLoader(c, 20)的時候,20並沒有傳到給寬度啊。因為參數是有順序的,而對象的屬性則安全得多。

(二) 定義對象的方式

源碼裡面定義lightLoader時使用的是經典的構造函數的方式,將屬性和函數都放在構造函數中,而粒子Particle的方法則是放在Particle的原型中定義的。這很關鍵!

經典構造函數帶來的問題可以自行百度,博客園上介紹也非常多,一搜一百頁。簡單來說就是構造函數內部的所有函數和屬性都會被覆制到每個實例中,比如說我通過構造函數創建了5個實例,那在記憶體中就有5份副本存在。但是很明顯,方法(不習慣說函數。。。)不應該被覆制5份,而應該是5個實例共用一個方法即可。所以,目前推薦的是使用混合模式定義對象:屬性放在構造函數中,方法放在原型中。對於數量較大(比如說本例中的粒子),那方法甚至屬性都應該放在原型中,以減少記憶體消耗,提高動畫流暢度。

雖然源碼那樣寫了, 但是我還是覺得lightLoader對象的方法也應該放到原型中,這是也是個代碼規範的問題。

(三)封裝問題

源碼中所有屬性都被定義為this.**,也就是說都暴露給外界了。這些屬性都是跟效果相關的,很多屬性需要看著效果調試出來的。暴露出來的好處就是調試的時候可以在運行時動態改變相應的值,觀察效果的變化,非常方便。你們感受一下:

此處應有圖

但並不是所有屬性都應該被暴露出來的,哪些需要暴露,哪些需要隱藏這個要看具體場景了。另外私有成員的命名潛規則(←.←)是前面加_,私有屬性和私有方法都應該這樣命名,這樣同類們一看到就懂啦。

封裝的另外一個方面是要與DOM對象松耦合,一個組件假如跟其他元素的聯繫很緊密的話,移植性就非常差了。這一點暫時我還沒太多體會,不敢妄議。

 

就說到這裡啦,看起來不是很有料呢。。。所以,還是補張圖片豐滿一下吧~碼字不易,順手點贊哈!

 此處應有圖

(圖片出處:著名攝影師 小張同學,轉載請註明)

原創文章,轉載請註明出處!本文鏈接:http://www.cnblogs.com/qieguo/p/5438380.html 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Stylus是一款需要編譯的css語言,所以其本身文件不能被html直接調用,需要要編譯為css文件後再進行日常的載入。 stylus是一款優秀的css編譯語言,需要node.js支持,第一步需要安裝node.js 問題:Windows調試時ctrl+d無效果 ctrl+c退出? 怎樣直接在wind ...
  • 前兩天編寫了一個前端頁面,在本機上顯示一切正常。不過在不斷的測試中,發現了一個嚴重的問題,如果圖片過大,會撐破div溢出來。再由於頁面是自適應頁面,根據不同解析度的顯示器會做出相應的div寬度調整,所以圖片即使不大,但是因解析度不同也會出現溢出的情況。 這裡探討總結一下解決方法。 首先我們先來做個簡 ...
  • CSS3新特性,相容性,相容方法總結 css3手冊 "css3手冊" 邊框 border radius 用於添加圓角效果 語法: 用長度值設置對象的圓角半徑長度。不允許負值 用百分比設置對象的圓角半徑長度。不允許負值 實例: 相容性: IE9+,Firefox4+,Chrome5+,Safari5+ ...
  • 1.rem和em、px 首先來說說em和px的關係 em是指字體高度 瀏覽器預設1em=16px,所以0.75em=12px;我們經常會在頁面上看到根元素寫的font-size:65%; 這樣em就成了16px*62.5=10em;這是顯示在頁面的字體大小是10px; 這樣12px=1.2em,10 ...
  • z-index在日常開發中算是一個比較常用的樣式,一般理解就是設置標簽在z軸先後順序,z-index值大的顯示在最前面,小的則會被遮擋,是的,z-index的實際作用就是這樣。 但是你真的瞭解z-index嗎?你知道它有什麼特性嗎?這裡先拋出幾個名詞:“層疊順序(stacking order)”,“ ...
  • 這個列表包括20個我們覺得是最有用的免費的 jQuery 插件,它們都是最具創新性和最省時省力的解決方案,很多都是現代化的設計和開發中碰到的問題的處理方案。如果你熟悉下麵列出的任何插件,請與我們的讀者分享您的意見,或者如果您知道的我們還沒有包括的話,請與我們分享在下麵的評論部分。 您可能感興趣的相關 ...
  • 我們把命名參數(arguments)視為局部變數,在向參數傳遞基本類型值時,如同基本類型變數的複製一樣,傳遞一個副本,參數在函數內部的改變不會影響外部的基本類型值。如: 在向參數傳遞引用類型的值時,會把這個值 在記憶體中的地址複製給一個局部變數,因此這個局部變數的變化會反映子啊函數的外部,例如: 這個 ...
  • 網+線下沙龍 | 移動APP模式創新:給你一個做APP的理由>> 好的 API 設計:在自描述的同時,達到抽象的目標。 設計良好的 API ,開發者可以快速上手,沒必要經常抱著手冊和文檔,也沒必要頻繁光顧技術支持社區。 流暢的介面 方法鏈:流暢易讀,更易理解 設置和獲取操作,可以合二為一;方法越多, ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...