提高网页性能的超实用方法

就是这么标题党哇咔咔咔~~

其实只是突然想总结的一些实用小技巧吧,以后想到什么再补充。

一.事件委托

经常遇到比如有一列<li></li>需要绑定点击事件,想想就很容易:

<ul id="ul">
	<li></li>
	<li></li>
	<li></li>
	<li></li>
</ul>

<script>
var oUl = document.getElementById('ul');
var aLi = oUl.getElementByTagName('li');
aLi.addEventListener('click',function(){ 
	alert(aLi.innerHTML);
})
</script>

可是,如果,我有100个li,那是要在100个li都绑定事件监听?

还是不要吧...

那么事件委托就可以上场了,顾名思义就是把这些事件委托给别人来做,这个别人一般为它的父元素。

<ul id="ul">
	<li></li>
	<li></li>
	<li></li>
	<li></li>
</ul>

<script>
var oUl = document.getElementById('ul');
// 事件侦听器绑定到整个容器上
  oUl.addEventListener('click', function(e) {
    if (e.target && e.target.nodeName === 'LI') {
      var aLi = e.target;
      alert(aLi.innerHTML);
    }
  });
</script>

jquery的事件委托可以参考我的另一篇博文:http://anchen1204.github.io/jqcli.html

二.函数防抖

window对象的resize、scroll事件,文字输入、自动完成的keyup事件,拖拽时的mousemove事件,它们有个共同点:会频繁地被触发,超乎你的想象。结果可能严重到导致频繁执行DOM操作至浏览器崩溃。

这个时候就要讨论下这问题了,什么是函数防抖?

// 简单的防抖动函数
function debounce(func, wait, immediate) {
    // 定时器变量
    var timeout;
    return function() {
        // 每次触发 scroll handler 时先清除定时器
        clearTimeout(timeout);
        // 指定 xx ms 后触发真正想进行的操作 handler
        timeout = setTimeout(func, wait);
    };
};

// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
    console.log("Success");
}

// 采用了防抖动
window.addEventListener('scroll',debounce(realFunc,500));
// 没采用防抖动
window.addEventListener('scroll',realFunc);

所以,就是500ms 内没有连续触发两次 scroll 事件,那么才会触发我们真正想在 scroll 事件中触发的函数,这就是函数防抖的原理了。

三.函数节流

上面说了函数防抖,有一种情况可能就有点问题,比如我们下面要讨论的图片懒加载。当然本意并不是希望用户滑到某个点后,停了一段时间,才把他滑过和可视区域的图片加载进来,这是函数防抖的方式就不合适了。

于是说到函数节流。

与防抖相比,节流函数最主要的不同在于它保证在 X 毫秒内至少执行一次我们希望触发的事件。

// 简单的节流函数
function throttle(func, wait, mustRun) {
    var timeout,
        startTime = new Date();

    return function() {
        var context = this,
            args = arguments,
            curTime = new Date();

        clearTimeout(timeout);
        // 如果达到了规定的触发时间间隔,触发 handler
        if(curTime - startTime >= mustRun){
            func.apply(context,args);
            startTime = curTime;
        // 没达到触发间隔,重新设定定时器
        }else{
            timeout = setTimeout(func, wait);
        }
    };
};
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
    console.log("Success");
}
// 采用了节流函数
window.addEventListener('scroll',throttle(realFunc,500,1000));

所以,就算500ms 内连续触发n次 scroll 事件,但保证我们的事件在1000ms内还是会被处理一次,这就是节流函数的原理啦。

关于以上两点的可见:http://www.codeceo.com/article/web-high-performance-scroll.html

另外补充一下,有个神奇的css属性叫pointer-events: none

之前用到的场景是头部有个酷炫的canvas动画,然后下面有优惠券需要点击,于是乎把canvas动画层设置这个属性,鼠标对页面的操作就不受到影响啦。

发现天猫首页滚动时,给body也加了这个属性,这是为了防止你的鼠标在滚动的时候停在页面上,会不断触发页面元素可能绑定的hover事件。

关于这个属性更多的文章:http://www.zhangxinxu.com/wordpress/2014/01/pointer-events-none-avoiding-unnecessary-paints/

另外,张大大还有另一篇讲关于这个属性对性能的提升讨论:http://www.zhangxinxu.com/wordpress/2014/01/pointer-events-none-avoiding-unnecessary-paints/ ,非常有趣,值得一读,也值得去实践。

四.图片懒加载

懒加载就是lazyload(这是一句废话)。比较常见于图片按需加载,几乎很容易就可以在网上找到例子,比如前东家京东的各种页面。当这些资源不在浏览器的可视区域时,先不用加载,等到适当的时候再去加载资源,避免网页打开时间过长,页面load太久影响用户体验。

实现原理:

1.为<img>标签设置一个暂存图片URL的自定义属性(例如data-url)存储图片实际地址,图片原本src里用一张1*1或者尺寸小的图统一代替,当图片出现在视口时,再将data-url的值赋给图片的src属性;

2.怎么判断“适当的时候”,这里只以竖直方向作为判断。

分享个插件http://www.appelsiini.net/projects/lazyload

五.图片预加载

预加载就是与懒加载相反的东西(这不是废话)。它是牺牲服务器前端性能来换取更好的用户体验,字面意思就是浏览器把图片下到本地,同样的src就会使用缓存。

function loadImage(url,callback) {
    var img = new Image();
    
    img.src = url;
    img.onload = function(){
        img.onload = null;
        callback.call(img);
    }
}

使用场景比如,头部动画元素较多或者有某个比较大,需要让图片预加载,以免影响动画效果,相信也不希望动画在播放的同时图片还在load吧。

比如我其中一个项目这么写:

/*头部动画图片预加载*/
    function preLoad(){
        var  iCount=0, 
        	 img1 = new Image(), 
        	 img2 = new Image(), 
        	 img3 = new Image(), 
        	 img4 = new Image(), 
        	 img5 = new Image(), 
        	 img6 = new Image();
        var arr = [img1,img2,img3,img4,img5,img6];
        for(var i=0;i<arr.length;i++){
            arr[i].onload=function(){
                iCount++;
                if(iCount==6){
                   $('.banner').addClass('ani-banner');
                }
            }
        }
        img1.src='地址';
        img2.src='地址';
        img3.src='地址';
        img4.src='地址';
        img5.src='地址';
        img6.src='地址';
    }
$(function(){
    preLoad();
});

六.重排和重绘(reflow和repaint)

首先复习一下浏览器中访问一个网址的页面呈现过程:

1.浏览器首先获取html页面;

2然后解析它的Dom结构;

3.接着解析css(css+Dom每个节点的位置和大小等几何信息,layout);

4.css paint(绘制成位图);

5.传输到GPU渲染到屏幕。

重排和重绘就发生在第3和第4步。

所以,在解析css开始的步骤,修改的css元素是在越前的步骤解析的话会越耗性能,因为它及其它后面的步骤将会被重新修改一遍。 换句话来说,Dom操作触发频繁reflow(layout)和repaint,layout发生在repaint之前,相对会造成更多性能损坏。

那么,哪些元素会影响layout和paint?

举些我们很常用的例子:

影响layout: margin/padding/width/height/display/border/top/left/right/bottom/position/font-size/text-align/line-height/font-weight/font-family/overflow...

影响repaint:

color/border-style/visibility/backgrounf/text-decoration/border-radius/outline/box-shadow/backgrounf-size...

注意:

设置动画元素为absolute或fixed:

七.css3动画性能优化

竟然被问住了,然后十分钟之后想起来我....

也写在这里吧,之前有探讨过一篇关于web渲染及硬件加速,其实就有提到动画性能的问题。

曾经遇到一个移动端问题,做的一个蝴蝶节活动,有比较多的入场的动画,然后做好后设计觉得可以更花哨,要求加上一大两小三只蝴蝶在循环infinite动画,我觉得会比较耗性能阻止未果后,还是加上去。

然后测试上线时,6p微信崩了..。

后来,动画不能砍,设计会跟我拼命,所以我只能再看看。

检查是不是我的dom尺寸太大导致绘制时造成内存的溢出...

把蝴蝶的循环位移left啊这些位移直接交给translate3d来做,开启硬件加速,但是不能滥用,会耗电...

检查动画是否占文档流,是的话改为绝对定位,减少重排和重绘...

把图片重新再压缩一遍...

...其他还做了什么我忘了,最后没啥问题了

另外,如果需要些js动画的,一般会用setTimeout,但是还有另外一个为动画而生的,叫requestAnimationFrame,比如http://anchen1204.github.io/demo/spball/ball-10.html,这里就使用了这个东西。好处在于setTimeout可能会出现掉帧卡顿现象,因为它在js内执行,js是单线程可能会有阻塞。而requestAnimationFrame相当于是把动画交给浏览器去执行,浏览器会基于当前页面情况来自行决定最佳的帧速率,从而更合理地使用CPU。但是,它兼容Android4.4及以上,IE10及以上,兼容性其实跟css3动画差不多,所以可能要根据需求配合setTimeout做降级处理。

未完待续...