使用javascript和css模拟帧动画的几种方法浅析

来源:互联网 发布:杭州seo外包 编辑:程序博客网 时间:2024/06/06 01:35

转载地址:http://blog.sina.com.cn/s/blog_830edcf30101mxma.html


我们平时在开发前端页面的时候,经常会播放一段帧序列。这段帧序列就像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.getElementByIdx_x('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.getElementByIdx_x(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++;                }             }         },                           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(){                                        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来实现功能。





0 0
原创粉丝点击