zoukankan      html  css  js  c++  java
  • 前端开发之

    总结下实现webapp动画的学习过程。

    首先复习下pc端:

    pc端动画离不开js计时器,无论是setInterval还是setTimeout。按照运动形式可以分为:匀速运动、缓冲运动、摩擦运动、以及其它高级运动。

    缓冲运动

    下面封装了一个缓冲运动:(源于秒味):

    function startMove(obj,json,endFn){
        clearInterval(obj.timer);    
        obj.timer = setInterval(function(){    
            var bBtn = true;
            for(var attr in json){
                var iCur = 0;
                if(attr == 'opacity'){
                    if(Math.round(parseFloat(getStyle(obj,attr))*100)==0){
                    iCur = Math.round(parseFloat(getStyle(obj,attr))*100);
                    }else{
                        iCur = Math.round(parseFloat(getStyle(obj,attr))*100) || 100;
                    }    
                }else{
                    iCur = parseInt(getStyle(obj,attr)) || 0;
                }
                var iSpeed = (json[attr] - iCur)/20;
                    iSpeed = iSpeed >0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
                if(iCur!=json[attr]){
                    bBtn = false;
                }
                if(attr == 'opacity'){
                    obj.style.filter = 'alpha(opacity=' +(iCur + iSpeed)+ ')';
                    obj.style.opacity = (iCur + iSpeed)/100;
                }else{
                    obj.style[attr] = iCur + iSpeed + 'px';
                }
            }
            if(bBtn){
                clearInterval(obj.timer);
                if(endFn){
                    endFn.call(obj);
                }
            }
        },30);
    }
    function getStyle(obj,attr){
        if(obj.currentStyle){
            return obj.currentStyle[attr];
        }else{
            return getComputedStyle(obj,false)[attr];
        }
    }
    View Code

    缓冲运动是js运动世界中的最简单的运动,因为它的速度一直会递减到1,在像素的世界里,这使得物体最后可以恰恰好好运动到终点的位置,一个像素不差。

    匀速运动:

    运动的过程中速度恒定不变,所以运动到最后不一定恰好到达终点:


    所以我们要在运动的最后增加判断,if下一次运动将超过终点(这里是400),就停止计时器,并把物体拉回到终点。示例如下:

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>无标题文档</title>
    <style>
    #div1 {width:100px; height: 100px; background: red; position: absolute; left: 0px; top: 30px;}
    </style>
    <script>
    window.onload = function() {
        var oBtn = document.getElementById('btn');
        var oDiv = document.getElementById('div1');
        var iTimer = null;
        
        oBtn.onclick = function() {
            startMove(30,400);
        }
        function startMove(speed,target) {
            clearInterval(iTimer);
            iTimer = setInterval(function() {
                if (oDiv.offsetLeft >= target) {
                    clearInterval(iTimer);
                    oDiv.style.left = target+'px';
                    console.log(target)
                } else {
                    oDiv.style.left = oDiv.offsetLeft + speed + 'px';
                }
            }, 30);
        }
    }
    </script>
    </head>
    <body>
        <input type="button" value="动起来" id="btn" />
        <div id="div1"></div>
    </body>
    </html>
    View Code

    //.....................这里补充pc端的其它运动........................

    那么在移动端我们如何做动画呢?

    首先认识下简单的touch事件:

    例如,我们做一个图片滑动切换效果,从来没有做过移动端的同学一般会把pc端做图片的思路把代码搬过来:

    无外乎是pc端的onmousedown、onmousemove、onmouseup等事件...等等 这里可是移动端,这些事件还好使吗?对头,在移动端我们使用的是touch事件:
    移动设备上面的click、mouseover、mousedown、mouseup扔然会触发,但是并不会真实的触发,在touchstart触发的300ms之后才会模拟触发click事件,mouseup、mousedown也将会在同一时刻触发。

    webkit内的触摸事件:

    event.touches数组是一组触摸事件所产生的触摸对象。

    touches对象的相关属性:

    触摸事件包含下列三个用于跟踪触摸的属性。

    • touches:表示当前跟踪的触摸操作的Touch对象的数组(页面上的所有触摸)。
    • targetTouches:特定于事件目标的Touch对象的数组(目标元素的所有当前触摸)。
    • changedTouches:表示自上次触摸以来发生了什么改变的Touch对象的数组(页面上最新更改的所有触摸-仅列出最后发生的触摸)。

    如果你在使用touchend或者gestureend事件,那么changedTouches这个属性 非常重要。在这两种情况下,屏幕上都不会再出现手指,因此targetTouches和touches应该为空,但我们仍然可以通过 changedTouches数组来了解最后发生的事情 。

    有了上面的一些参数,我们可以根据pc端的思路来组织一套代码:

    setInterval版图片切换:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>。。。</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    /*photo*/
    .photo {position:relative;width:100%;height:200px;overflow:hidden;}
    .photo .number{color:#fff;background:#000;padding:6px 10px;position:absolute;right:0;bottom:0;}
    .photo .number .page_now,
    .photo .number .page_all{padding:0 2px;}
    
    .photo_tit_box{position: absolute;bottom: 0px;left:0px;width:100%;height: 30px;background: #000;opacity:0.8;}
    .photo_tit{color: #fff;line-height: 30px;padding: 0 5px;float: left;width:70%;overflow:hidden;text-overflow:ellipsis;white-space: nowrap;}
    .photo_tit_page{float: right;margin-top: 12px;}
    .photo_tit_page li{height: 8px;line-height:8px;font-size:0px;width:8px;background: #fff;border-radius: 4px;background: #fff;float: left;margin-right: 5px;opacity:0.5;}
    .photo_tit_page li.active{opacity:1;}
    
    .detailpic_ul{position:absolute;top:0px;left:0px;}
    .detailpic_li{height:200px;text-align:center;background:#e1e1e1;float:left;background: #aad8f3;overflow: hidden;}
    .detailpic_li img{display:inline-block;height:200px;}
    /*photo end*/
    </style>
    <script src="http://192.168.34.59:8080/target/target-script-min.js#anonymous"></script>
    </head>
    <body style="height:1000px;">
        <div class="photo">
            <ul class="detailpic_ul">
                <li class="detailpic_li"><a href="http://www.baidu.com">1<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">2<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">3<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">4<img src="pic/2-1.jpg" alt="" /></a></li>
            </ul>
        </div>
            
    <script>
    
    function getStyle(obj,attr){
        if(obj.currentStyle){
            return obj.currentStyle[attr];
        }
        else{
            return getComputedStyle(obj,false)[attr];
        }
    };
    
    //缓冲类型运动 setInterval 设置
    function startMove(obj,json,endFn){
        clearInterval(obj.timer);
        obj.timer = setInterval(function(){
            var bBtn = true;
            for(var attr in json){
                var iCur = 0;
                var speed = 0;
                if(attr == 'opacity'){
                    if(Math.round(parseFloat(getStyle(obj,attr))*100)==0){
                        iCur = Math.round(parseFloat(getStyle(obj,attr))*100);
                    }
                    else{
                        iCur = Math.round(parseFloat(getStyle(obj,attr))*100) || 100;
                    }    
                }else{
                    iCur = parseInt(getStyle(obj,attr)) || 0;
                }
                iSpeed = (json[attr] - iCur)/4;
                iSpeed = iSpeed >0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
                if(iCur!=json[attr]){
                    bBtn = false;
                }
                if(attr == 'opacity'){
                    obj.style.filter = 'alpha(opacity=' +(iCur + iSpeed)+ ')';
                    obj.style.opacity = (iCur + iSpeed)/100;
                }
                else{
                    obj.style[attr] = iCur + iSpeed + 'px';
                }
            }
            if(bBtn){
                clearInterval(obj.timer);
                if(endFn){
                    endFn.call(obj);
                }
            }
        },30);
    };
    
    //详细页面 图片切换
    function HousePicQh(option){
        this.ulClass=option.ulClass;
        this.index=0; //记录index
    }
    //fn : 滑动完之后执行的函数
    HousePicQh.prototype.init=function(){
        var _this=this;
        var winW=document.documentElement.clientWidth || document.body.clientWidth;
        var ulW=0;
        _this.oUl=document.querySelector("."+_this.ulClass);
        _this.oLi=_this.oUl.getElementsByTagName("li");
        _this.liLen=_this.oLi.length;
        _this.liW=winW;
        
        //初始化 ul li 宽度
        for(var i=0;i<_this.liLen;i++){
            _this.oLi[i].style.width=winW+"px";
            ulW+=winW;
        };
        _this.oUl.style.width = ulW+"px";
        _this.oUl.addEventListener("touchstart",function(ev){
                var touchs = ev.touches[0];
                _this.downX=touchs.clientX;
                _this.downL=this.offsetLeft;
                _this.oUl.addEventListener("touchmove",function(ev){
                    var touchs = ev.touches[0];
                    var nowX=touchs.clientX;
                    var L=_this.downL+nowX-_this.downX;
                    clearInterval(this.timer)
                    this.style.left=L+"px";
                });
                //ev.preventDefault();
            })
        _this.oUl.addEventListener("touchend",function(ev){
            var touchs = ev.changedTouches[0];
            var nowX=touchs.clientX;
            var disL=nowX-_this.downX;
            if(Math.abs(disL)>30){ //默认 滑动需要超过30
                disL>0?_this.index--:_this.index++;
                _this.index=_this.index==-1?0:_this.index;
                _this.index=_this.index==_this.liLen?_this.liLen-1:_this.index;
                var L=-_this.index*_this.liW;
                //运动
                startMove(_this.oUl,{"left":L});
                _this.oUl.onmousemove=null;
            };
        });
    };
    
    
    //传入参数:ul的class名
    var HousePicQh1=new HousePicQh({"ulClass":"detailpic_ul"});
    HousePicQh1.init();
    
    </script>
    
    </body>
    </html>
    View Code

    上面代码中touchstart事件最后并没有阻止默认事件,如果阻止了默认事件,设备自带的页面滚动不会生效,点击图片上面的链接也不会跳转,不阻止的话会不会在不同的设备上有其它问题...这个怎么处理我也一直再找答案。

    另外:touchend中的 var touchs = ev.changedTouches[0] 如果改成 var touchs = ev.touches[0] 是无效的,原因就是应为触发touchend后屏幕上面已经没有触摸点,所以这里只能用changedTouches捕获页面最后发生的触摸,这个需要注意下。

    上面的代码是完全按照pc端做动画的思路来的,借用了这篇文章最开始我们封装的缓冲运动函数,这样的动画用到移动端会导致抖动,原因是:

    在所有的浏览器中,js是单线程的,在某一时刻只能有一条语句执行,事件定时器等异步任务中,会加入到执行队列,然后等事件变得空闲时执行。如果浏览器在忙,需要等到空闲之后,下一帧的动画才会绘制,这样就造成了动画的抖动(不流畅)。可以想象,如果我们定时器中的代码执行太慢,会降低界面的相应速度,特别是在性能比较低的移动设备上面。所以我们应该尽量避免使用定时器(setInterval、setTimeout)来完成动画。

    transition

    我们来试试css过渡transition,它可以对付我们大多数的动画要求,让我们对上面的代码做一点改进。

    我们要做的就是动态设置transition的值:(transition已经成为标准,要不要添加前缀取决于你想支持的浏览器版本)

    --在touchend的时候设置 ul的样式: "-webkit-transition:left .2s ease-out; transition:left .2s ease-out; ",实现touchend后的动画;

    --在touchmove的时候,我们是不需要这样的过渡的,我们需要的是图片列表紧密跟随手机移动,所以设置 "-webkit-transition:none; transition:none; "来取消过渡。

    代码如下:

    transition版图片切换:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    /*photo*/
    .photo {position:relative;width:100%;height:200px;overflow:hidden;}
    .photo .number{color:#fff;background:#000;padding:6px 10px;position:absolute;right:0;bottom:0;}
    .photo .number .page_now,
    .photo .number .page_all{padding:0 2px;}
    
    .photo_tit_box{position: absolute;bottom: 0px;left:0px;width:100%;height: 30px;background: #000;opacity:0.8;}
    .photo_tit{color: #fff;line-height: 30px;padding: 0 5px;float: left;width:70%;overflow:hidden;text-overflow:ellipsis;white-space: nowrap;}
    .photo_tit_page{float: right;margin-top: 12px;}
    .photo_tit_page li{height: 8px;line-height:8px;font-size:0px;width:8px;background: #fff;border-radius: 4px;background: #fff;float: left;margin-right: 5px;opacity:0.5;}
    .photo_tit_page li.active{opacity:1;}
    
    /*图片切换*/
    .detailpic_ul{position:absolute;top:0px;left:0px;}
    .detailpic_li{height:200px;text-align:center;background:#e1e1e1;float:left;background: #aad8f3;overflow: hidden;}
    .detailpic_li img{display:inline-block;height:200px;}
    /*photo end*/
    </style>
    </head>
    <body style="height:1000px;">
        <div class="photo">
            <ul class="detailpic_ul">
                <li class="detailpic_li"><a href="http://www.baidu.com">1<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">2<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">3<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">4<img src="pic/2-1.jpg" alt="" /></a></li>
            </ul>
        </div>
            
    <script>
    
    //详细页面 图片切换
    function HousePicQh(option){
        this.ulClass=option.ulClass;
        this.index=0; //记录index
    }
    //fn : 滑动完之后执行的函数
    HousePicQh.prototype.init=function(){
        var _this=this;
        var winW=document.documentElement.clientWidth || document.body.clientWidth;
        var ulW=0;
        _this.oUl=document.querySelector("."+_this.ulClass);
        _this.oLi=_this.oUl.getElementsByTagName("li");
        _this.liLen=_this.oLi.length;
        _this.liW=winW;
        
        //初始化 ul li 宽度
        for(var i=0;i<_this.liLen;i++){
            _this.oLi[i].style.width=winW+"px";
            ulW+=winW;
        };
        _this.oUl.style.width = ulW+"px";
        _this.oUl.addEventListener("touchstart",function(ev){
                var touchs = ev.touches[0];
                _this.downX=touchs.clientX;
                _this.downL=this.offsetLeft;
                _this.oUl.addEventListener("touchmove",function(ev){
                    var touchs = ev.touches[0];
                    var nowX=touchs.clientX;
                    var L=_this.downL+nowX-_this.downX;
                    this.style.webkitTransition="none"; //新添加
                    this.style.transition="none"; //新添加
                    this.style.left=L+"px";
                });
                //ev.preventDefault();
            })
        _this.oUl.addEventListener("touchend",function(ev){
            var touchs = ev.changedTouches[0];
            var nowX=touchs.clientX;
            var disL=nowX-_this.downX;
            if(Math.abs(disL)>30){ //默认 滑动需要超过30
                disL>0?_this.index--:_this.index++;
                _this.index=_this.index==-1?0:_this.index;
                _this.index=_this.index==_this.liLen?_this.liLen-1:_this.index;
                var L=-_this.index*_this.liW;
                this.style.webkitTransition="left .2s ease-out"; //新添加
                this.style.transition="left .2s ease-out"; //新添加
                this.style.left=L+"px"; //新添加
                _this.oUl.onmousemove=null;
            };
        });
    };
    
    
    //传入参数:ul的class名
    var HousePicQh1=new HousePicQh({"ulClass":"detailpic_ul"});
    HousePicQh1.init();
    
    </script>
    
    </body>
    </html>
    View Code

    如果我想再动画完成之后添加一个回调函数,改怎么处理?例如我想在每一次图片切换完之后log一下当前显示的是第几张图片,改如何传入我的回调函数呢?

    试试transitionend吧

    首先需要针对不同的浏览器版本添加不同的前缀,并赋给eventName

    var trans="transition";
    var eventName="transitionend";
    var transitions = {  
          'transition':'transitionend',  
          'OTransition':'otransitionend',  
          'MozTransition':'transitionend',  
          'WebkitTransition':'webkitTransitionEnd',  
          'MsTransition':'MSTransitionEnd'  
        }
    for (t in transitions){
        if(document.body.style[t]){
            trans = t;
            eventName = transitions[t];
            break;
        }
    }

    剩下的就简单了,只需要给图片列表ul添加事件就可以了

    _this.oUl.addEventListener(eventName,function(ev){
            console.log(_this.index+1)
        });

    完整代码:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    /*photo*/
    .photo {position:relative;100%;height:200px;overflow:hidden;}
    .photo .number{color:#fff;background:#000;padding:6px 10px;position:absolute;right:0;bottom:0;}
    .photo .number .page_now,
    .photo .number .page_all{padding:0 2px;}
    
    .photo_tit_box{position: absolute;bottom: 0px;left:0px;100%;height: 30px;background: #000;opacity:0.8;}
    .photo_tit{color: #fff;line-height: 30px;padding: 0 5px;float: left;70%;overflow:hidden;text-overflow:ellipsis;white-space: nowrap;}
    .photo_tit_page{float: right;margin-top: 12px;}
    .photo_tit_page li{height: 8px;line-height:8px;font-size:0px;8px;background: #fff;border-radius: 4px;background: #fff;float: left;margin-right: 5px;opacity:0.5;}
    .photo_tit_page li.active{opacity:1;}
    
    /*图片切换*/
    .detailpic_ul{position:absolute;top:0px;left:0px;}
    .detailpic_li{height:200px;text-align:center;background:#e1e1e1;float:left;background: #aad8f3;overflow: hidden;}
    .detailpic_li img{display:inline-block;height:200px;}
    /*photo end*/
    </style>
    </head>
    <body style="height:1000px;">
        <div class="photo">
            <ul class="detailpic_ul">
                <li class="detailpic_li"><a href="http://www.baidu.com">1<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">2<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">3<img src="pic/2-1.jpg" alt="" /></a></li>
                <li class="detailpic_li"><a href="http://www.baidu.com">4<img src="pic/2-1.jpg" alt="" /></a></li>
            </ul>
        </div>
            
    <script>
    //添加浏览器前缀
    var trans="transition";
    var eventName="transitionend";
    var transitions = {  
          'transition':'transitionend',  
          'OTransition':'oTransitionEnd',  
          'MozTransition':'transitionend',  
          'WebkitTransition':'webkitTransitionEnd',  
          'MsTransition':'msTransitionEnd'  
        }
    for (t in transitions){
        if(document.body.style[t]){
            trans = t;
            eventName = transitions[t];
            break;
        }
    }
    
    
    //详细页面 图片切换
    function HousePicQh(option){
        this.ulClass=option.ulClass;
        this.index=0; //记录index
    }
    //fn : 滑动完之后执行的函数
    HousePicQh.prototype.init=function(){
        var _this=this;
        var winW=document.documentElement.clientWidth || document.body.clientWidth;
        var ulW=0;
        _this.oUl=document.querySelector("."+_this.ulClass);
        _this.oLi=_this.oUl.getElementsByTagName("li");
        _this.liLen=_this.oLi.length;
        _this.liW=winW;
        
        //初始化 ul li 宽度
        for(var i=0;i<_this.liLen;i++){
            _this.oLi[i].style.width=winW+"px";
            ulW+=winW;
        };
        _this.oUl.style.width = ulW+"px";
        _this.oUl.addEventListener("touchstart",function(ev){
                var touchs = ev.touches[0];
                _this.downX=touchs.clientX;
                _this.downL=this.offsetLeft;
                _this.oUl.addEventListener("touchmove",function(ev){
                    var touchs = ev.touches[0];
                    var nowX=touchs.clientX;
                    var L=_this.downL+nowX-_this.downX;
                    this.style.webkitTransition="none"; 
                    this.style.transition="none"; 
                    this.style.left=L+"px";
                });
                //ev.preventDefault();
            })
        _this.oUl.addEventListener("touchend",function(ev){
            var touchs = ev.changedTouches[0];
            var nowX=touchs.clientX;
            var disL=nowX-_this.downX;
            if(Math.abs(disL)>30){ //默认 滑动需要超过30
                disL>0?_this.index--:_this.index++;
                _this.index=_this.index==-1?0:_this.index;
                _this.index=_this.index==_this.liLen?_this.liLen-1:_this.index;
                var L=-_this.index*_this.liW;
                this.style.webkitTransition="left .2s ease-out"; 
                this.style.transition="left .2s ease-out"; 
                this.style.left=L+"px"; 
                _this.oUl.onmousemove=null;
            };
        });
        //下面是新添加代码
        _this.oUl.addEventListener(eventName,function(ev){   
            console.log(_this.index+1)
        });
    };
    
    
    //传入参数:ul的class名
    var HousePicQh1=new HousePicQh({"ulClass":"detailpic_ul"});
    HousePicQh1.init();
    
    </script>
    
    </body>
    </html>
    View Code

    transitionend的使用还有一个问题:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    .obox{width:50px;height: 50px;background: #c50000;position: absolute;top:300px;left: 0px;  z-index: 10;-webkit-transition: all 0.25s ease-in; }
    .obox.on{ left:100px;opacity: 0.5;}
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox"></div>
        <script>
        //添加浏览器前缀
        var trans="transition";
        var eventName="transitionend";
        var transitions = {  
              'transition':'transitionend',  
              'OTransition':'oTransitionEnd',  
              'MozTransition':'transitionend',  
              'WebkitTransition':'webkitTransitionEnd',  
              'MsTransition':'msTransitionEnd'  
            }
        for (t in transitions){
            if(document.body.style[t]){
                trans = t;
                eventName = transitions[t];
                break;
            }
        }
    
        var n=0
        var obox=document.querySelector("#obox");
        obox.addEventListener(eventName,function(ev){   
            console.log(++n) //这里执行了两次
        });
        obox.addEventListener("click",function(){
            this.classList.add("on")
        })
    
        </script>
    </body>
    </html>
    View Code

    代码中的console.log(n++)执行了两次,也就是transitionend执行了两次。

    因为物体添加class"on"后,实现了两个属性的过渡:left和opacity;如果将"-webkit-transition: all 0.25s ease-in"中的"all"改成"opacity"或者"left",就只会执行一次了。

    这个往往和我们需要的效果相违背,我们一般需要的是等物体所有的状态执行完后,只执行一次transitionend。如何解决这个问题?

    1、可以记录物体过渡的状态个数m,在脚本的头部声明一个变量n,每次执行transitionend时n++,如果n==m,则执行我们需要的回调函数:

    对上个示例的代码稍加改造:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    .obox{width:50px;height: 50px;background: #c50000;position: absolute;top:300px;left: 0px;  z-index: 10;-webkit-transition: all 0.25s ease-in; }
    .obox.on{ left:100px;opacity: 0.5;height: 100px;width:50px;}
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox"></div>
        <script>
        //添加浏览器前缀
        var trans="transition";
        var eventName="transitionend";
        var transitions = {  
              'transition':'transitionend',  
              'OTransition':'oTransitionEnd',  
              'MozTransition':'transitionend',  
              'WebkitTransition':'webkitTransitionEnd',  
              'MsTransition':'msTransitionEnd'  
            }
        for (t in transitions){
            if(document.body.style[t]){
                trans = t;
                eventName = transitions[t];
                break;
            }
        }
    
        var n=0; //记录完成的状态数目
        var m=3 //需要过渡完成的状态总数
        var obox=document.querySelector("#obox");
        obox.addEventListener(eventName,function(ev){   
            n++;
            if(n==m){  
                console.log(n);
            }
        });
        obox.addEventListener("click",function(){
            this.classList.add("on")
        })
        </script>     
    </body>
    </html>
    View Code

     2、记录完成整个过渡需要的时间t,设定setTimeout函数的时间间隔t1==t,在setTimeout中执行相关相关回调函数:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    .obox{width:50px;height: 50px;background: #c50000;position: absolute;top:300px;left: 0px;  z-index: 10;-webkit-transition: all 0.2s ease-in; }
    .obox.on{ left:100px;opacity: 0.5;height: 100px;width:50px;}
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox"></div>
        <script>
        var delay=200; 
        var obox=document.querySelector("#obox");
        obox.addEventListener("click",function(){
            this.classList.add("on");
            setTimeout(function(){
                console.log(1)
            },delay)
        })
        </script>     
    </body>
    </html>
    View Code

    感觉两个方法都很勉强,不知道有没有更好的解决办法?你要是看到了这里,碰巧你知道更好的解决办法,那么,留言吧。

    下面我们来模仿一个失重状态下的小球:

    利用传统计时器实现:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    .obox{width:50px;height: 50px;line-height: 50px;text-align: center; background: #c50000;position: absolute;top:100px;left: 100px;border-radius: 50%;color: #fff;}
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox">点击我</div>
        <script>
            var obox=document.querySelector("#obox");
            obox.onclick=function(){
                 drop(this)
            }
            function drop(obj){
                var speed=0; 
                var a=10;             //加速度
                var d=document.documentElement || document.body;
                var winH=d.clientHeight;
                var h=obj.offsetHeight;
                var target=winH-h;   //目标点
                var timer=null;
                var nowH=obj.offsetTop;
                clearInterval(timer);
                timer=setInterval(function(){
                    speed+=a;
                    nowH += speed;
                    if(nowH == target && speed==0){
                        clearInterval(timer);
                        return false;
                    }
                    if(nowH >= target){
                        nowH = target;
                        speed*=-1;
                        console.log(speed)
                    }
                    obj.style.top = nowH+"px";
    
                },30)
            }
        </script>
    </body>
    </html>
    View Code

    利用transition实现:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    .obox{width:50px;height: 50px;line-height: 50px;text-align: center; background: #c50000;position: absolute;top:100px;left: 100px;border-radius: 50%;color: #fff;}
    
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox">点击我</div>
        <script>
            //添加前缀
            var trans="transition";
            var eventName="transitionend";
            var transitions = {  
                  'transition':'transitionend',  
                  'OTransition':'oTransitionEnd',  
                  'MozTransition':'transitionend',  
                  'WebkitTransition':'webkitTransitionEnd',  
                  'MsTransition':'msTransitionEnd'  
                }
            for (t in transitions){
                if(document.body.style[t]){
                    trans = t;
                    eventName = transitions[t];
                    break;
                }
            }
    
            //小球运动
            var obox=document.querySelector("#obox");
            var down=1; //区分下落还是上抛 标记
            var o = obox.offsetTop;
            var b = (document.documentElement.clientHeight || document.body.clientHeight) -obox.offsetHeight;
            obox.onclick=function(){
                var _this=this;
                drop(this);
                obox.addEventListener( eventName,function(){
                    drop(_this);
                    console.log(1)
                })
            }
            function drop(obj){
                if(down){
                    obj.style[trans]="top .3s cubic-bezier(1,0,0.96,0.91)";
                    obj.style.top = b+"px";
                    down = 0;
                }else{
                    obj.style[trans]="top .3s cubic-bezier(0,.27,.32,1)";
                    obj.style.top = o+"px";
                    down = 1;
                }
            }
        </script>
    </body>
    </html>
    View Code

    利用transition实现实现的例子中,ease-in、ease-out、ease-in-out不是我们要的效果,所以例子中用到了-立方赛贝尔,这里是一个非常强大的在线调试工具,你可以随心所欲的调出自己想要的效果。

    这篇文章可以参考《贝塞尔曲线与CSS3动画、SVG和canvas的基情》

    利用transition实现实现的例子中,效果并不完美,现实生活中会有摩擦损耗和碰撞损耗,且现实中每次运动所用的时间也不是恒定的。

    css动画animation

    关键帧规则需要为每一个浏览器制造商前缀重写一遍

    animation实现上面的动画(重力作用下的小球)可以完全不用js。

    首先利用@keyframe创建关键帧:

    @-webkit-keyframes fall{
        0%{
            top:100px;
            -webkit-animation-timing-function:cubic-bezier(1,0,.96,.91);
        }
        50%{
            top:300px;
            -webkit-animation-timing-function:cubic-bezier(1,.27,.32,1);
        }
        100%{
            top:100px;
            -webkit-animation-timing-function:cubic-bezier(0,.27,.32,1);
        }
    }

    将关键帧应用于选择器:

    .obox{
            width:50px;
            height: 50px;
            line-height: 50px;
            border-radius: 50%;
            color: #fff;
            text-align: center;
            background: #c50000;
            position: absolute;
            top:100px;
            left: 100px;
            -webkit-animation:fall .3s  infinite;
    }

    时间函数 animation-timing-function 可以应用到选择器的动画上,或者不同的时间函数可以应用到@keyframe规则中的任意一帧上,注意:定义在关键帧中的样式会覆盖定义到选择器中的样式。

    完整实例代码:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    
    @-webkit-keyframes fall{
        0%{
            top:100px;
            -webkit-animation-timing-function:cubic-bezier(1,0,.96,.91);
        }
        50%{
            top:300px;
            -webkit-animation-timing-function:cubic-bezier(1,.27,.32,1);
        }
        100%{
            top:100px;
            -webkit-animation-timing-function:cubic-bezier(0,.27,.32,1);
        }
    }
    .obox{
            width:50px;
            height: 50px;
            line-height: 50px;
            border-radius: 50%;
            color: #fff;
            text-align: center;
            background: #c50000;
            position: absolute;
            top:100px;
            left: 100px;
            -webkit-animation:fall .3s  infinite;
    }
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox"></div>
    </body>
    </html>
    View Code

    更强大的requestAnimationFrame(请求动画帧 )

    可以看下这篇文章 CSS3动画那么强,requestAnimationFrame还有毛线用

     这个东西其实和 setTimeout 功能类似,只不过是使用方法不同,requestAnimationFrame 只需要一个回调函数作为参数,回调函数会在下一次浏览器重绘之后执行,不需要再人为的指定时间间隔。

    requestAnimationFrame优点

    1、浏览器自行优化帧速率在内的东西,性能提高。跟着浏览器的绘制走,如果浏览设备绘制间隔是16.7ms,那就这个间隔(16.7ms)绘制;如果浏览设备绘制间隔是10ms, 就10ms绘制。这样就不会存在过度绘制的问题,动画不会掉帧,自然流畅。

    2、可以达到完美的向下兼容(自己封装)。

    3、可以用到css3不能涉及的地方(scrollTop等)。

    4、可以支持更多的动画效果(可以支持css3不能支持的 Back、 Bounce 等缓动效果)。

    缺点:需要前缀、不能在android浏览器上工作!(可以封装函数来解决)

    浏览器支持情况:

    我们来创建一个跨浏览器的请求帧方法:

    var requestFrame = (function(){
            var sFun=null,
                prefixList=['webkit','moz','ms'];
    
            for(var i=0;i<prefixList.length;i++){
                sFun=prefixList[i]+"RequestAnimationFrame";
                if(window[sFun]){
                    return function(fn){
                        window[sFun](fn);
                    }
                }
            };
            return function(fn){
                setTimeout(fn,1000/60); //视频游戏多是60帧
            };
        })()

    或者可以这样:

    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              function( callback ){
                window.setTimeout(callback, 1000 / 60);
              };
    })();

    下面示例中我们将小球从0移动到300,每帧10:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    
    .obox{
            width:50px;
            height: 50px;
            line-height: 50px;
            border-radius: 50%;
            color: #fff;
            text-align: center;
            background: #c50000;
            position: absolute;
            top:0px;
            left: 100px;
            -webkit-animation:fall .3s  infinite;
    }
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox"></div>
    
    <script>
        var requestFrame = (function(){
            var sFun=null,
                prefixList=['webkit','moz','ms'];
    
            for(var i=0;i<prefixList.length;i++){
                sFun=prefixList[i]+"RequestAnimationFrame";
                if(window[sFun]){
                    return function(fn){
                        window[sFun](fn);
                    }
                }
            };
            return function(fn){
                setTimeout(fn,1000/60); //视频游戏多是60帧
            };
        })()
    
           var oBox=document.querySelector("#obox");
           var start=0;
           var target=300;
           var speed=10;
           var L=0;
           move();
           function move(){
               console.log(1)
               L = L+speed;
               oBox.style.top = L+"px";
               if(L<target){
                   requestFrame(move)
               }
           }
    </script>
    </body>
    </html>
    View Code

    上面这段代码执行时间的长短会因游览器的不同而不同,如何保证动画执行的总时间跟我们需要的一致?

    我们可以定义时间戳,然后在浏览器重绘的时候计算时间差值,进而算出每帧的速度:speed=(target-origin)/ t ;t为浏览器每次重绘的时间间隔;

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    
    .obox{
            width:50px;
            height: 50px;
            line-height: 50px;
            border-radius: 50%;
            color: #fff;
            text-align: center;
            background: #c50000;
            position: absolute;
            top:300px;
            left: 100px;
    }
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox"></div>
    
    <script>
        var requestFrame = (function(){
            var sFun="requestAnimationFrame",
                prefixList=['webkit','moz','ms'];
            if(!window[sFun]){
                for(var i=0;i<prefixList.length;i++){
                    sFun=prefixList[i]+"RequestAnimationFrame";
                    if(window[sFun]){
                        return function(fn){
                            window[sFun](fn);
                        }
                    }
                };
            }
            return function(fn){
                setTimeout(fn,1000/60); //视频游戏多是60帧
            };
        })()
    
           var oBox=document.querySelector("#obox");
           var origin=0;
           var target=300;
           function animate(obj,origin,target,duration,fn){
               var L=origin;
               var beginT=new Date().getTime();
               var speed;
               move();
               function move(){
                   var endT=new Date().getTime();
                   var diffT=endT-beginT;
                   beginT=endT;
                   speed=(target-origin)/duration*diffT;speed=Math.ceil(speed);
                   if( Math.abs(target -L) > Math.abs(speed) ){
                       L = L+speed;
                       obj.style.top = L+"px";
                       requestFrame(move);
                   }else{
                       obj.style.top = target+"px";
                       fn && fn();
                   }
    
               }
           }
    
           animate(oBox,origin,target,1000)
    </script>
    </body>
    </html>
    View Code

    这个理论上可以,但是经过测试其实还是有误差的,不同的浏览器每帧的时间不一样,同一个浏览器每次重绘需要的时间也不是总是恒定的。

    css变换transform

    上面的代码中都是通过改变物体的top值来实现动画,transform变换则不同,它是在DOM被计算后,改变元素的绘出方式,它可以实现位移、缩放、旋转、倾斜等变化,类似ps中的ctrl+t。

    transform的3D变换会使浏览器开启设备上面的GPU(设备内置的图形加速硬件),经过硬件加速的变换是目前最快的动画。-开启3d变换常被用来提高动画性能。比较下面两个示例代码:

    这里只是将translate改为translate3d,并增加一个为0的Z值,这个在第三维度没有实质的变换,却开启了GPU。

    2d变换:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    @-webkit-keyframes move{
        0%{
            -webkit-transform:translate(0,0px);
            -webkit-animation-timing-function:cubic-bezier(1,0,0.96,0.91);
        }
        50%{
            -webkit-transform:translate(0,300px);
            -webkit-animation-timing-function:cubic-bezier(1,0.27,0.32,1);
        }
        100%{
            -webkit-transform:translate(0,0px);
            -webkit-animation-timing-function:cubic-bezier(0,0.27,0.32,1);
        }
    }
    .obox{
            width:50px;
            height: 50px;
            line-height: 50px;
            border-radius: 50%;
            color: #fff;
            text-align: center;
            background: #c50000;
            position: absolute;
            top:100px;
            left: 100px;
            -webkit-animation:1s move infinite linear;
    }
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox"></div>
    </body>
    </html>
    View Code

    3d变换:

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
      <meta name="apple-mobile-web-app-capable" content="yes">
      <meta content="black" name="apple-mobile-web-app-status-bar-style">
      <meta name="format-detection" content="telephone=no">
      <meta content="telephone=no" name="format-detection">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
      <title>...</title>
    <style>
    *{padding: 0px;margin: 0px;font-size: 12px;}
    @-webkit-keyframes move{
        0%{
            -webkit-transform:translate3d(0,0px,0);
            -webkit-animation-timing-function:cubic-bezier(1,0,0.96,0.91);
        }
        50%{
            -webkit-transform:translate3d(0,300px,0);
            -webkit-animation-timing-function:cubic-bezier(1,0.27,0.32,1);
        }
        100%{
            -webkit-transform:translate3d(0,0px,0);
            -webkit-animation-timing-function:cubic-bezier(0,0.27,0.32,1);
        }
    }
    .obox{
            width:50px;
            height: 50px;
            line-height: 50px;
            border-radius: 50%;
            color: #fff;
            text-align: center;
            background: #c50000;
            position: absolute;
            top:100px;
            left: 100px;
            -webkit-animation:1s move infinite linear;
    }
    </style>
    </head>
    <body style="height:1000px;">
        <div class="obox" id="obox"></div>
    </body>
    </html>
    View Code

    translate3d可以用来加速我们的动画,但是仍然建议在动画完成后将3d转换为2d,也就是清除动画完成后清除3d变换。原因如下:

    1、经过translate3d变换显示的GPU渲染的图像,而不是实际内容,如果你做的动画是放大的话,那么放大后就会模糊。

    2、GPU的内存空间也是有限的,也会被填满,如果我们页面上用了过多的translate3d变换,浏览器会崩溃。

    上面两个问题都可以通过transitionend、animationend事件将3d转换为2d来解决。

    综上:做动画,移动端尽量不要再使用setTimeout定时器,我们可以选择css3的transition、animation配合transform来实现:transition可以实现两个状态之间的过渡;animation可以定义动画帧、利用requestAnimationFrame(请求动画帧)完成我们需要的大多数复杂动画;translate3d还可以为我们开启浏览器的GPU渲染,来加速动画。

    结语:最近尝试了移动端webapp的一个小项目,从pc端转到移动端感觉各种坑,同时也体会了新技术的强大。

     



  • 相关阅读:
    其他技术----mongoDB基础
    redis学习----Redis入门
    网络通信学习----HTTP请求方法
    spring boot 学习 ---- spring boot admin
    java拓展----(转)synchronized与Lock的区别
    spring boot 学习 ---- spring MVC
    解决ubuntu的apt-get命令被占用
    阴暗
    图像分割实战-视频背景替换
    「知乎」你们觉得响应式好呢,还是手机和PC端分开来写?
  • 原文地址:https://www.cnblogs.com/hdchangchang/p/3966653.html
Copyright © 2011-2022 走看看