百度前端技术学院是一个为大学生创办的免费的前端技术实践、分享、交流平台。由百度校园招聘组、百度校园品牌部、百度前端技术部以及多个百度的前端团队联合创办。学院组织了一批百度在职工程师,精心编写了数十个实践编码任务,将技术知识点系统有机地串联在各个充满趣味与挑战的任务中,同学们通过实际地编码练习来掌握知识,再辅以互相评价、学习笔记等方式,加深对于学习内容的理解。在过去的三年中,百度前端技术学院累积吸引了上万名同学参加,并且有数十名同学在学习后,顺利加入了百度,成为了百度的前端工程师。

封装DOM动画类库学到的东西

作者吕晓军课程封装DOM动画类库(一)2224次浏览42017-03-23 09:06

以前做动画的时候,常常会卡顿, 猜,大致的原因可能是, 浏览器绘制页面的时候, 反应不过来,所以会出现卡顿。汗!!,然后,今天通过这个任务, 了解到了requestAnimationFrame ,通过百度才知道具体的原因。下面是我自己的理解,如果有不对的地方,大家即时指出来,方便我即时的修改。

浏览器绘制页面的时候, 主要是通过两个线程去绘制页面的。 一个是主线程, 一个排版线程。

主线程

通常情况下, 主线线程的负责工作是: 运行javascript 、 计算HTML元素的css样式 、 把页面的元素绘制成一个 或者 多个位图。然后, 把这些位图 移交给 排版线程

(百度了下位图,我把它理解成了用像素点组成的图片, 反正我理解的就是图像。)

排版线程

通常情况下, 拿到主线程 传过来的 位图以后, 排版线程主要做一下这么几个工作:

  1. 通过CPU渲染位图, 在屏幕上面显示。
  2. 主线程 请求更新位图的可见部分 或者 即将可见的部分。
  3. 判断出当前页面 出于可见的部分, 判断出即将通过页面滚动而可见的部分 、 随着用户滚动页面来移动这些部分。

CPU

排版线程通过CPU把位图绘制在页面当中。

CPU比较擅长的工作: 绘制位图到屏幕上。 在不同位置, 以不同角度,不同大小,(我感觉可以理解为同一张图,变形之类的)。
CPU相对慢的地方 : 将位图加载到显存里面。

讲完上面的现在浏览器就开始绘制页面了。

浏览器下载完页面中所有的组件以后。(组件包括:HTML标签,javascript,css,图片.... 等)

会 生成2个内部数据,也就是我们 通常说的树, 一个是DOM树, 一个是渲染树。

DOM树 代表的是页面的结构 。 渲染树 代表的是 DOM节点如何显示。

DOM树 中的每一个需要显示的DOM节点渲染树中 至少都对应着一个节点。(DOM树中隐藏的节点(display:none)在渲染树中是没有对应节点的 )。然后,渲染树的节点,就会生成一个盒模型,没错,就是我们打开 f12 右边看到的那个盒模型。

当DOM树 和 渲染树构建完成以后, 浏览器就开始绘制页面了。(怎么绘制页面呢?就是我上面说的通过主线程和排版线程绘制页面)

现在重点来了

当DOM发生变化,影响了元素的几何属性(宽和高), 那么浏览器会重新计算元素的几何属性,同样其他元素的几何属性和位置也会受到影响。这个时候,浏览器 原来的渲染树中受影响的部分就失效了,所以渲染树需要重新构造, 这个过程其实就重排。完成重排以后,浏览器会把受影响的部分重新绘制在屏幕上,这个过程就是重绘。

到这里你肯定恍然大悟了,反正我就是这样。

这些不断的重排重绘 , 会导致 界面 卡顿。(不断重排重绘 ,其实就是 主线程 和 排版线程之间不断的请求数据(比如不断的请求宽度),如果特别频繁的话(大部分浏览器的显示频率为16.667ms),超过16.667ms肯定会卡。就比如高速公路每秒最多能通过10辆车,突然来了一大批不明车辆,那不就堵车了么,大概是这个意思 )。

很显然,每次都重排都会导致重绘, 那些情况会导致重排呢?(提升性能的关键)

  1. 增加或者删除 可见的DOM元素
  2. 元素的位置发生改变
  3. 元素的宽高发生改变。
  4. 元素的内容发生改变(比如你 一个元素里面高度不固定,里面有一段问题,突然的你把文字替换成了一个张大图片,那不就把元素撑大了么)
  5. 页面初始化渲染( 这个避免不了 )
  6. 浏览器窗口发生变化的时候。

下面就是关于requestAnimationFrame

用法就不说了,网上面一大堆。说下原理。

用js 做动画,通常都是setTimeoutsetInterval。 大部分浏览器的显示频率为16.667ms( 1000/60 = 16.6666 );意思就是 每秒60帧。 如果做动画的时候我们这样写:

setInterval(function(){ 

},10)

明显要小于16.667,所以这个情况就和上面说的堵车一样,本来是16.667ms才能做的事情,非要10ms做完,这不就堵了么,所以说强扭的瓜不甜

所以W3C就推出了 requestAnimationFrame 这么个东西,它是怎么做的呢?
requestAnimationFrame是不需要使用者指定循环间隔时间,它会自动的根据当前浏览器的性能 自行去决定最佳的帧速率。所以我才明白为啥任务要让用requestAnimationFrame了。

下面就说说我怎么去一步步构建一个动画库。(ps:我基本算个菜鸟)

我是 先百度了上面的内容才开始写动画库,虽然自己以前也写过类似jq的animate,但是那个有BUG,并且正如上面所说的,setInterval和setTimeout写出来的动画,始终会有卡顿的情况,所以我果断的 参考了Velocity.js 的写法。

其实是被这两句话骗过去的。

兼容性
Velocity可以随处可见 - 回到IE8和Android 2.3。在引擎盖下,它模仿jQuery的$ .queue(),从而与jQuery的$ .animate(),$ .fade()和$ .delay()无缝地进行交互操作。由于Velocity的语法与$ .animate()相同,您的代码不需要更改。
加载
Velocity在所有级别的压力下都胜过jQuery,并且优于Transit(领先的CSS动画库),从中等压力水平开始。

大体意思就是兼容IE8 比jquery动画快。 其实也很正常,jq本身就不适合做动画,大家都懂,在手机上面更是卡的不行。

然后我就吸取了上面任务的经验,( 大家还记得vue第一个任务吗? 布置任务的老师说他怎么解析vue源码。)然后就在github找到velocity.js, 然后从版本0.0开始解析。

不得不吐槽一下啊, 以前解析别人库源码,傻乎乎的从最新版本解析,整的一脸懵比。这里贴个 velocity.js 0.0版本的链接

我没有去研究那些 需求分析啊, 接口文档设计啊,之类的东西,因为1, 我没有参考对象,不知道要设计个什么样子的。2 , 我感觉我就算研究了也不懂。 所以我写的时候,准备仿人家velocity.js。 可以说是盗版,嘿嘿!

在写之前我测试了下requestAnimationFramesetTimeout的区别, 点击document触发
很明显看出用requestAnimationFrame做出来的动画要明显平稳的多。

因为requestAnimationFrame存在兼容性,然后我就直接把velocity.js的兼容处理直接拔下来了,大家别笑。

//因为requestAnimationFrame和setTimeout一样都是全局函数可以直接调用的。
//这段兼容的意思就是 如果浏览器兼容requestAnimationFrame,就使用requestAnimationFrame,否则就 用setTimeout来代替requestAnimationFrame。
// timeDelta 这里时间的计算最终结果为0, 这是js setTimeout 的一个小技巧。大致意思就是 普通队列执行完成以后,才顺序执行setTimeout队列
var requestAnimationFrame = window.requestAnimationFrame || (function() {
    var timeLast = 0;

    return window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) {
                var timeCurrent = (new Date()).getTime(),
                        timeDelta;

                /* Dynamically set delay on a per-tick basis to match 60fps. */
                /* Technique by Erik Moller. MIT license: https://gist.github.com/paulirish/1579671 */
                timeDelta = Math.max(0, 16 - (timeCurrent - timeLast) );
                timeLast = timeCurrent + timeDelta;

                return setTimeout(function() { callback(timeCurrent + timeDelta); }, timeDelta);
            };
})();

因为我是准备这么调用的。

$("#div3").velocity({
            opacity : "30"
        },{
            date : 1000,
            type : "linear",
            callback : function(){
                console.log( "回调" )
            }
        });

所以开始的时候 , 我是这么写的。

(function(window){

        //模拟一个jquery $。
        window.$ = function( obj ){

            var objList = document.querySelectorAll( obj );
            var len = objList.length;
            if( len > 1 ){

                for(var i=0; i<len; i++){
                    return new Velocity( objList[i] );
                }

            }else{
                return new Velocity( objList[0] );
            }
        };

        //创建Velocity 构造函数
        function Velocity( obj ){
            this.obj = obj;
        };

        //Velocity添加方法及属性
        Velocity.prototype = {

            constructor : Velocity,

            velocity : function( options ){
                console.log( options )
            }

        }

    })(window);

然后就是animate的实现。动画形式利用的是贝塞尔曲线, animate实现的核心思想 就是在规定的时间内做完事情。

animate : function(){
            var json = this.json;
            var obj = this.obj;
            var date = this.settings.date;
            var fx = this.settings.type;
            var fn = this.settings.callback;
            var _this = this;

            var iCur = {} , startTime = ( new Date() ).getTime() ;
            for( var attr in json ){
                if( attr === 'opacity' ){
                    iCur[attr] = Math.round( css( obj , 'opacity') * 100 );
                }else{
                    iCur[attr] = parseInt( css( obj , attr ) );
                }
            };

            requestAnimationFrame( animation );
            function animation(){
                var changeTime = ( new Date() ).getTime();
                var t = date - Math.max( 0 , ( startTime - changeTime + date ) );

                for(var attr in json){

                    var value = Tween[fx]( t , iCur[attr] , ( json[attr] - iCur[attr] ) , date );

                    if( attr === 'opacity' ){
                        obj.style.opacity = value/100;
                        obj.style.fliter = 'alpha(opacity='+value+')'
                    }else{
                        obj.style[attr] = value + 'px';
                    }

                };

                if( t === date){

                    fn && fn.call( obj );
                    return;
                };
                requestAnimationFrame( animation );
            };

        }

t 其实就是代表的当前时间段。 比如,你规定一个动画4s执行完, 动画刚开始t就为0 ,结束的时候为4 ,中间是逐步增大的。

下面案例地址:
dome

还有好多功能没实现, 比如stop()啊, fadeIn()啊之类的,还有很多bug。不过大体思路是这样 , 剩下的优化慢慢做。

有不足的大家多提意见。

0条评论