使用javascript和css模擬幀動畫的幾種方法淺析


我們平時在開發前端頁面的時候,經常會播放一段幀序列。這段幀序列就像gif圖片那樣,反復循環播放。那大家可能會說,直接用gif圖片就好了,干嘛還去模擬呢?那是因為要做得更加靈活,我們要做到以下幾點:

1、我們希望這段幀動畫只循環播放所指定的次數。

2、我們希望幀動畫結束的瞬間執行某種操作。這個在游戲中大量存在。

3、我們想自如的控制播放的速度。

4、我們想盡可能讓這個幀動畫的實現方式兼容大部分瀏覽器,在移動和pc端都能運行良好。

有了以上四點要求,那就不是gif圖片所能完成的了。下面,我們先探討有哪些技術可以實現我所說的功能。首先我們先准備好一張幀序列圖片。如下圖所示:

一、使用CSS3動畫。

CSS3動畫的timing-function里有一個step-end方式,可以不用緩慢過渡,而是直接以跳幀的方式實現變化。這個方式我認為是最省事的辦法了,然而在CSS3還未完全兼容的時代,step-end的兼容性更加差。就不說IE,我在智能機的幾種基於webkit的低版本瀏覽器中測試時也發現一些不兼容現象。考慮到css3的普及速度,此種方式依然值得大家學習。具體代碼實現參考如下(篇幅考慮僅列出webkit的寫法):

<html>
    <head>
        <style type="text/css">
            #anim{
                background-image: url(anim.png);
                width:120px;
                height:120px;
                -webkit-animation: auto-circle 0.5s step-end infinite;
            }

            @-webkit-keyframes auto-circle{
                0%{
                  background-position-x: 0;
                }
                20%{
                  background-position-x: 120px;
                }
                40%{
                  background-position-x: 240px;
                }
                60%{
                  background-position-x: 360px;
                }
                80%{
                  background-position-x: 480px;
                }
                100%{
                  background-position-x: 600px;
                }
            }
        </style>
    </head>
    <body>
        <div id="anim">
        </div>
    </body>
</html>

以上代碼可以在chrome瀏覽器中正常運行,然而,不知大家注意到一個問題沒有。從0%到100%,其實只播放了幀動畫的5幀,第6幀沒有播放。這是因為100/6無法得到整數值,所以無法均等分割。這也是這種方式的局限之一。由於蘋果谷歌對translate2d和translate3d都有較好的支持甚至硬件加速,為了得到更好的性能,我們可以不用background-position,而使用CSS3中的Transforms。當然,這需要外層套一個overflow:hidden;的div。改善后的代碼如下所示:

<html>
    <head>
        <style type="text/css">
            #animbg{
                width:120px;
                height:120px;
                overflow: hidden;
            }
            #anim{
                -webkit-animation: auto-circle 0.5s step-end 0 5;
            }

            @-webkit-keyframes auto-circle{
                0%{
                  -webkit-transform:translate3d(0,0,0);
                }
                20%{
                  -webkit-transform:translate3d(-120px,0,0);
                }
                40%{
                  -webkit-transform:translate3d(-240px,0,0);
                }
                60%{
                  -webkit-transform:translate3d(-360px,0,0);
                }
                80%{
                  -webkit-transform:translate3d(-480px,0,0);
                }
                100%{
                  -webkit-transform:translate3d(-600px,0,0);
                }
            }
        </style>
    </head>
    <body>
        <div id="animbg">
            <img id="anim" src="anim.png"></div>
        </div>
    </body>
    <script type="text/javascript">
        document.addEventListener('webkitAnimationEnd',function(){
            document.getElementById('animbg').style.display = 'none';
        });
    </script>
</html>

最后的js代碼中的webkitAnimationEnd就是用來綁定當css動畫結束后的動作。

二、使用canvas

說道幀動畫,很容易就聯想到canvas。用drawImage方法將圖片繪制到canvas上面,不斷重繪就能得到我們想要的效果。使用canvas的好處是,只要有一個基於canvas模擬幀動畫的類庫,就可以隨意使用。操作js比操作css要靈活,可以傳遞各種參數實現不同的要求,還可以使用回調函數來設置動畫結束時的操作。缺點是老式瀏覽器不兼容canvas,而且如果需求簡單的話,使用canvas有些大材小用。一個最簡單的循環動畫類如下所示:

function MovieClip(x, y, img, width, height, totalFrame, fps)
{
    this.x = x;
    this.y = y;
    this.img = document.getElementById(img);
    this.time = 0;
    this.frame = 0;
    this.width = width;
    this.height = height;
    this.totalFrame = totalFrame;
    this.fps = fps || 1;
}

MovieClip.prototype.draw = function()
{
    this.time ++;
    if(this.time % this.fps == 0)
    {
        this.frame ++;
        if(this.frame == this.totalFrame) this.frame = 0;
    }

    var frame = this.frame;
    context.drawImage(this.img, this.frame * 120, 0, 120, 120, this.x, this.y, this.width, this.height);
}

這段代碼來自於王勁的html5小游戲billd源碼,對canvas感興趣的人可以去看看http://06wjin.sinaapp.com/html5/billd/

我也寫了一個簡單的操作工具庫,可以用來操作canvas元素。代碼如下:

(function(){
     window['canvasgif'] = {
        canvas:null,//canvas元素
        context:null,//canvas環境
        fps:30,//幀頻
        loopCount:1,//循環次數
        tempCount:0,//當前的循環次數,用來計數
        
         img_obj : {//圖片信息保存變量
            'source': null,
            'current': 0,
            'total_frames': 0,
            'width': 0,
            'height': 0
        },

         init:function(canvas,imgsrc,frames,fps,loopCount,fn){//初始化canvas和圖片信息
             var me = this;
            me.canvas = canvas;
            me.context = canvas.getContext("2d");
            me.fps = fps || 30;
            me.loopCount = loopCount || 1;
            var img = new Image();
            img.src = imgsrc || 'anim.png';
            img.onload = function () { 
                me.img_obj.source = img;
                me.img_obj.total_frames = frames;
                me.img_obj.width = this.width/frames;
                me.img_obj.height = this.height;
                me.loopDraw(fn);
            }
         },
         draw_anim:function(context,iobj){//繪制單張圖片
             if (iobj.source != null){
                 context.drawImage(iobj.source, iobj.current * iobj.width, 0,
                    iobj.width, iobj.height,0, 0, iobj.width, iobj.height);
                iobj.current = (iobj.current + 1) % iobj.total_frames;
                //如果不是無限循環,則需要計算當前循環次數
                if(this.loopCount != -1 && iobj.current == iobj.total_frames - 1){
                    this.tempCount++;
                }
             }
         },
         
         /**
         * 入口執行方法,對外的接口
         * @param canvas        canvas的dom對象,可通過getElementById得到,或jquery方法$('')[0]
         * @param imgsrc        所繪制圖片的路徑,如'media/event/altar.png'
         * @param frames        圖片的幀數
         * @param fps           繪制的幀頻,如5,10,30等,數字越大,繪制速度越快
         * @param loopCount     繪制的循環次數,-1為無限循環
         * @param fn            如果不是無限循環則繪制結束時的回調函數
         *
         * 使用示例:window.canvasgif.render(canvas,imgsrc,12,5,1,function(){});
         *
         */
         render:function(canvas,imgsrc,frames,fps,loopCount,fn){
            this.init(canvas,imgsrc,frames,fps,loopCount,fn);
         },

         loopDraw:function(fn){//循環繪制圖片
            var me = this;
            var ctx = me.context;
            var pic = me.img_obj;
            var width = me.canvas.width,height = me.canvas.height;
            var intervalid = setInterval((function(){
                ctx.clearRect(0,0,width,height);
                 me.draw_anim(ctx,pic);
                if(me.loopCount != -1 && me.tempCount == me.loopCount){
                    me.tempCount = 0;
                    clearInterval(intervalid);
                    ctx.clearRect(0,0,width,height);
                    typeof fn == "function" &&  fn();
                }
            }),1000/this.fps);
         }
     }
 })();

三、使用javascript操作css屬性

第三種方法是最保險的方法,因為既不使用css3,也不使用canvas,保證兼容了大部分的瀏覽器。思路很簡單,就是靠javascript不斷的改變幀圖片的background-position。這里為了方便起見,使用的jquery。代碼如下:

<html>
    <head>
        <style type="text/css">
            #animbg{
                background-image: url(anim.png);
                width:120px;
                height:120px;
                background-repeat: no-repeat;
            }
        </style>
        <script type="text/javascript" src="jquery-1.4.4.min.js"></script>
        <script type="text/javascript">
        (function(window){
            window.frameAnimation = {
                anims:(function(){
                    /*
                    obj=>需要執行背景動畫的對象;
                    width:圖片的總寬度
                    steps=>需要的幀數;
                    eachtime=>一次完整動畫需要的時間;
                    times=>動畫執行的次數 0表示無限反復
                    */
                    return function(obj,width,steps,eachtime,times, callback){
                        var runing = false;
                        var handler = null;         //obj,width,steps,eachtime,times定時器
                        var step = 0;       //當前幀
                        var time = 0;       //當前第幾輪
                        var speed = eachtime*1000/steps;      //間隔時間
                        var oneStepWidth = width/steps;
                        
                        function _play(){
                            if(step >= steps){
                                    step = 0;
                                    time++;
                            }
                            if(0 == times || time <times){
                                obj.css('background-position', -oneStepWidth * step + 'px 0px');
                                step++;
                            }else{
                                control.stop();
                                callback && callback();
                            }
                        }
                        
                        var control = {
                            start:function(){
                                if(!runing){
                                    runing = true;
                                    step = time = 0;
                                    handler = setInterval(_play, speed);
                                }
                                return this;
                            }
                          
                            ,stop:function(restart){
                                if(runing){
                                    runing = false;
                                    if(handler){
                                        clearInterval(handler);
                                        handler = null;
                                    }
                                    if(restart){
                                        obj.css('background-position', '0 0');
                                        step = 0;
                                        time = 0;
                                    }
                                }
                            }
                            ,dispose:function(){
                                this.stop();
                                //console.log('anim dispose');
                            }
                        };
                        return control;
                    }
                })()
            }
        })(window);

        function play(){
            var anim = frameAnimation.anims($('#animbg'),720,6,1,0);
            anim.start();
        }
        
    </script>
    </head>
    <body onload="play()">
        <div id="animbg"></div>
    </body>
</html>

總結:

三種方法各有優勢和劣勢。如果明確了瀏覽器的型號和版本支持css3時,推薦使用第一種方法。如果是為了廣泛使用,推薦使用第三種方法。當序列幀很簡單的時候,不建議使用canvas來實現功能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM