zoukankan      html  css  js  c++  java
  • 使用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=>需要执行背景动画的对象;
                        图片的总宽度
                        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来实现功能。

    原文链接

  • 相关阅读:
    将SqlServer的数据导出到Excel/csv中的各种方法 .
    SqlServer: 单用户模式下查杀相关进程实现单/多用户转换 .
    SQL Server游标的使用【转】
    由几行代码浅析C#的方法参数传递
    脑力风暴之小毛驴历险记(1)好多胡萝卜(下)
    关于sql_mode=only_full_group_by问题的解决方法
    如何同一个controller下添加新页面
    UNIAPP全局变量的实现方法
    Ztree点击节点选中复选框的相关操作
    一沙框架(yishaadmin) 前端引入VUE的实现方法
  • 原文地址:https://www.cnblogs.com/kevinxue/p/5643073.html
Copyright © 2011-2022 走看看