天天看點

撲面而來的碎片--圖檔3D炸裂效果初體驗

直念念不忘想要自己做一個3D效果的爆炸動效。這兩天在搞一些小動畫,就順便也把3D爆炸做了出來。原理很簡單,就是用很多小圖檔拼湊成大圖檔,然後讓小圖檔按照一定規律運動形成爆炸效果。這裡的爆炸效果用的是 CSS3 的 3D 變換來做的,通過 js 動态改變變換參數形成動畫。實作步驟簡單分為兩步:圖檔拼湊和爆炸實作。

 之前逛園子的時候看到 ChokCoco 的爆炸效果作品:【BOOM】一款有趣的Javascript動畫效果 (大神英文有沒有拼錯呀←.←),覺得蠻有意思的,效果如下:

撲面而來的碎片--圖檔3D炸裂效果初體驗
不過覺得這個爆炸效果還是偏軟了一點,沒有爆炸那種碎片飛濺的感覺,一直念念不忘想要自己做一個3D效果的爆炸動效。這兩天在搞一些小動畫,就順便也把3D爆炸做了出來,動畫效果:
撲面而來的碎片--圖檔3D炸裂效果初體驗

實作

原理很簡單,就是用很多小圖檔拼湊成大圖檔,然後讓小圖檔按照一定規律運動形成爆炸效果。這裡的爆炸效果用的是 CSS3 的 3D 變換來做的,通過 js 動态改變變換參數形成動畫。實作步驟簡單說說:

1、圖檔拼湊

這一步相對簡單,用的是很多 div 标簽的背景圖拼湊的,設定好每個 div 标簽的 position 和 background-position 就可以了。這裡要注意的一點就是添加 div 标簽是記得要用 innerHTML 一次性全部添加進去。雖然這裡沒有直接顯示圖檔,但是這裡還是 new 了一個 image,并将圖檔拼湊放在 load 事件中執行。效果和代碼分别如下(實際效果沒格子線的):

撲面而來的碎片--圖檔3D炸裂效果初體驗
撲面而來的碎片--圖檔3D炸裂效果初體驗
撲面而來的碎片--圖檔3D炸裂效果初體驗
var img = new Image();
img.src = 'img/zoro.jpg';    //160*160,or you need to change wrapper's size
img.onload = function () {
    var x = y = 0,
    div = styleCtn = '',
    imgWidth = this.width,
    imgHeight = this.height,
    pwidth = pheight = 10,
    nx = Math.floor(imgWidth / pwidth),    //x方向粒子個數
    ny = Math.floor(imgHeight / pheight),    //y方向粒子個數
    wrap = document.getElementById('zd-wrap');
                    
    for (var i = 0, num = nx * ny; i < num; i++) {
        x = (i % nx) * pwidth;
        y = Math.floor(i / ny) * 10;
    styleCtn = 'left: ' + x + 'px; top: ' + y + 'px; background-position: ' + (-x) + 'px ' + (-y) + 'px;';
    div = div + '<div class="bomb" style="background-image: url(' + this.src + '); ' + styleCtn + '"></div>';
    }
    wrap.innerHTML = div;    //添加圖檔
};      

View Code

2、爆炸效果

這一步相對難了許多,因為都是三維的運動,分為平移和翻轉兩種運動會簡單一點。

a)平移運動:在立體空間中的爆炸應該是這樣的:

撲面而來的碎片--圖檔3D炸裂效果初體驗

回到平面空間應該是這樣的:

撲面而來的碎片--圖檔3D炸裂效果初體驗

從上圖可以總結出小塊的運動方向,根據小塊所處的位置運動方向是不同的,左上角向左上角飛去,右下角向右下角飛去這樣。這裡應該有一部分小塊向螢幕飛來,還有一部分遠離螢幕以突出撲面而來的感覺。程式設計中表現各軸運動的是 translate3d,這裡注意螢幕和圖檔的坐标關系,螢幕的 Y 軸就是現實中的垂直方向、向下為正,Z軸就是面向使用者方向。運動總結起來就是:

左上角:vx < 0, vy < 0, vz随機

左下角:vx < 0, vy > 0, vz随機

右上角:vx > 0, vy < 0, vz随機

右下角:vx > 0, vy > 0, vz随機

撲面而來的碎片--圖檔3D炸裂效果初體驗

這裡 Y 方向沒有嚴格對半分,有一種整體向上的感覺。加速度的話模仿需要模仿重力: vxa = 0, vya = 0.5 (模仿重力),vza可以适當加點,能加強撲面而來的感覺。

b)翻轉運動

翻轉效果,就是 X 軸的旋轉,為了效果更加逼真需要引入 Y 軸的旋轉,可以忽略 z 軸的旋轉。因為粒子小塊的尺寸很小,是以這裡不需要嚴格控制旋轉參數,一方面簡化模型,另一方面減輕浏覽器負荷。當然也不必引入旋轉變量了,直接用 x、y 的坐标代入也可以得到不錯的翻轉效果: rotateX(xdeg) rotateY(ydeg),我這裡引用了 Zachstronaut 的算法:

rotateX: Math.cos(0.1 *ys) + 'rad
rotateY: Math.sin(0.1 * xs) + 'rad      

最後設定終止條件,可以根據 粒子小塊的x、y 坐标判斷是否應該讓其退出動畫循環。

這樣就實作了動畫,當然還必須要開啟父對象的透視屬性,大概設定透視距離 300px 左右就可以了。

後話

我使用的圖檔尺寸為160*160,粒子尺寸為10*10,在iOS中表現優秀,移動chrome中表現的也還不錯。雖然已經調用了 GPU 加速渲染,但已經到了很多國産移動浏覽器的上限了,是以不建議再增加粒子數量。性能提升也做了,但是沒找到好的突破點,如果你有更好的點子,請聯系我!

耗時測試:

撲面而來的碎片--圖檔3D炸裂效果初體驗
撲面而來的碎片--圖檔3D炸裂效果初體驗

可以看到瓶頸在 Painting 上,再細分的話主要是圖層重組,接着看一下動畫過程:

撲面而來的碎片--圖檔3D炸裂效果初體驗

可以看到在最開始的時候出現了密密麻麻的綠框,也就是這裡發生了大量的 paint flashing(重繪)。再看我們一開始拼湊背景圖的方法,用的是絕對定位+ left + top,這裡會導緻大量的重繪。雖然你可以在最開始用一個 translate3d(0, 0, 0)來限定渲染層讓綠框消失,但并不會有多大的效果,因為主要耗時的是圖層合成,并不是繪制。分層是必須的,我也嘗試過使用 translate 去代替 left + top,但是效果并不理想,暫時沒有想到更好的辦法改善渲染性能……

DOM操作方面倒是可以再改善,現在動效的代碼是:

this.nodes[i].style[this.transformProperty] = 'translate3d(' + this.xs[i] + 'px, ' + this.ys[i] + 'px, ' + this.zs[i] + 'px) rotateX(' + Math.cos(0.1 * this.ys[i]) + 'rad) rotateY(' + Math.sin(0.1 * this.xs[i]) + 'rad)';      

循環中每次都會操作dom,而且在設定style上還是用屬性查找的方式,那這裡應該是可以改善的。一是重寫style,避免查找屬性。二就是重寫父對象div裡面的innerHTML,就像開頭設定背景圖一樣,一次更新所有粒子塊。

不過我在步進調試的時候發現,除了第一次執行時會一個一個地設定粒子塊的屬性,後面的動畫循環中都已經被浏覽器優化成整體重寫了,每次更新都是全體更新的,是以上面的方法貌似也不能提升太多。真的沒想到其他優化的辦法了,如果你有點子,請聯系我!

如何使用

源碼已經放到GitHub(bomb.js)上面去了,有興趣的同學可以fork來看看,求星星!

我已經将 js+HTML+CSS 都封裝好了,設定好容器之後直接引用bomb.js就可以了,如下:

撲面而來的碎片--圖檔3D炸裂效果初體驗
撲面而來的碎片--圖檔3D炸裂效果初體驗
<style type="text/css">
    .wrapper {
        width: 160px;
        margin: 100px auto 0;
        position: relative;
        cursor: pointer;
        perspective: 200px;
    }
</style>

<div class="wrapper" id="zd-wrap"></div>
        
<script src="js/bomb.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
    var explore = new ParticlesTemplate(),
        exploreImg = new Image(),
        wrapper = document.getElementById('zd-wrap');
    exploreImg.src = 'img/zoro.jpg';    
    exploreImg.onload = function () {
        explore.init(exploreImg, wrapper);
        wrapper.addEventListener('click', function () {
            explore.go();
        }, false);
    };
</script>      

就寫到這了,碼字不易,随手點贊哈~~~

參考資料:

1) 【BOOM】一款有趣的Javascript動畫效果

2) 前端性能優化(CSS動畫篇)

3) http://www.zachstronaut.com/

撲面而來的碎片--圖檔3D炸裂效果初體驗

(圖檔出處:小周)

原創文章,轉載請注明出處!本文連結:http://www.cnblogs.com/qieguo/p/5491192.html 

繼續閱讀