scroll事件的優化以及scrollTop的兼容性

scrollTop的兼容性

scroll事件,當用戶滾動帶滾動條的元素中的內容時,在該元素上面觸發。<body>元素中包含所加載頁面的滾動條。html

雖然scroll事件是在window對象上發生,但他實際表示的則是頁面中相應元素的變化。在混雜模式(document.compatMode的值爲BackCompat)下,能夠經過<body>元素的scrollLeft和scrollTop來監控到這一變化。前端

而在標準模式(document.compatMode的值爲CSS1Compat)下,除Safari以外的全部瀏覽器都會經過<html>元素來反應這一變化。chrome

以上內容來自《Javascript 高級程序設計(第三版)》。segmentfault

如下是我本身測試的結果,截止2017-05-18,用的是最新版的chrome、Firefox和Win7中的IE。瀏覽器

  • 混雜模式下,chrome、IE、Firefox都是經過document.body.scrollTop監聽滾動條的位置。
  • 標準模式下,chrome經過document.body.scrollTop監聽滾動條位置,IE和Firefox經過document.documentElement.scrollTop監聽滾動條位置

能夠用下面的代碼進行驗證:app

    function outPutScrollTop() {
        console.log(document.compatMode);
        if(document.compatMode === 'CSS1Compat') {
            console.log(document.documentElement.scrollTop + '標準模式');
            console.log(document.body.scrollTop + '標準模式');
        } else {
            console.log(document.body.scrollTop + '混雜模式');
        }
    }
    // 綁定監聽
    window.addEventListener('scroll', outPutScrollTop);

(去掉文檔頭部的文檔聲明就能夠開啓混雜模式。)前端性能

scroll事件的優化

scroll事件若是不作優化,默認狀況下會頻繁地被觸發,若是在事件處理程序內進行了複雜的DOM操做,就會大大增長瀏覽器的負擔,消耗性能。函數

經過看別人的文章,知道了能夠經過防抖函數和節流函數對這種頻繁觸發的事件進行優化。post

防抖函數達成的效果是:scroll事件被頻繁觸發時,不會每次都執行事件處理程序中的關鍵代碼,當滾動條中止滾動時,通過事先設置好的時間間隔後纔會執行真正想要執行的代碼。性能

節流函數不像防抖函數那樣只在用戶中止滾動時才執行事件處理程序中的關鍵代碼,而是在用戶滾動滾動條的過程當中每隔必定的時間必執行一次事件處理程序中的關鍵代碼。

如下是對別人文章的總結。

防抖函數

簡單的防抖優化:

    function test() {
        console.log('func');
    }

    window.addEventListener('scroll',function(event) {
            clearTimeout(test.timer);
            test.timer = setTimeout(test,500);
    },false);

將上面的代碼封裝成一個函數:

function debounce(fun,t,immediate) {
    var timeout;
    //返回真正的scroll事件的事件處理程序
    return function(event) {
        var that = this, arg = arguments;
        var later = function() {
            timeout = null;
            if(!immediate) fun.apply(that,arguments);
        };
        var callNow = immediate && !timeout;//這一句位置很重要
        clearTimeout(timeout);
        timeout = setTimeout(later,t);
        if(callNow) {
            fun.apply(that,arguments);
        }
    }
};

debounce函數接收三個參數:

第一個參數是一個函數,該函數是事件處理程序中真正想要執行的代碼。

第二個參數是數字,單位毫秒,表示間隔多久調用一次做爲第一個參數的函數。這個參數不能小於當前瀏覽器的最小時間間隔(不一樣的瀏覽器的最小時間間隔不一樣,通常在10~20毫秒,HTML5規範中規定是4毫秒),若是這個參數等於或小於這個最小時間間隔,那麼和沒有優化沒有區別。事實上,未優化時,scroll事件頻繁觸發的時間間隔也是這個最小時間間隔。

第三個參數是一個布爾值,不傳或爲false時,最終的效果與開始那個簡單的防抖優化的效果同樣;當爲true時,表示滾動開始時執行一次做爲第一個參數的函數,滾動中止時不執行。

用法:

var myEfficientFn = debounce(function() {
    // 滾動中的真正想要執行的代碼
    console.log('ok' + new Date());
}, 500, false);
 
// 綁定監聽
window.addEventListener('scroll', myEfficientFn);

 下面是underscore.js裏封裝的防抖函數:

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
  var timeout, args, context, timestamp, result;

  var later = function() {
    var last = _.now() - timestamp;

    if (last < wait && last >= 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };

  return function() {
    context = this;
    args = arguments;
    timestamp = _.now();
    var callNow = immediate && !timeout;
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      result = func.apply(context, args);
      context = args = null;
    }

    return result;
  };
};

 節流函數

簡單的節流函數:

function throttle(fun,t,mustRun,denyLast) {
    var timer = null;
    var startTime = 0;
    return function(event) {
        var that = this, args = arguments;
        clearTimeout(timer);
        var later = function() {
            timer = null;
            if(denyLast) fun.apply(that,args);
            console.log('執行的是later.');
        };
        var currTime = new Date().getTime();
        if(currTime - startTime >= mustRun) {
            console.log(currTime - startTime);
            fun.apply(that,args);
            startTime = currTime;
        } else {
            timer = setTimeout(later,t);
        }
    };
}

這個節流函數的總體結構與防抖函數的相似,相比防抖函數,節流函數內部多了一個對時間間隔的判斷。

上面這個節流函數接收四個參數:

第一個參數是一個函數,表示當scroll事件被觸發時,開發者真正想要執行的關鍵代碼。

第二個參數是一個數字,單位毫秒,其實是要傳入setTimeout()方法的第二個參數。(這裏setTimeout()的做用就是防止事件處理程序中的關鍵代碼頻繁地執行)

第三個參數也是一個數字,單位毫秒,表示在該時間段內必執行一次關鍵代碼。

第四個參數是一個布爾值,表示在滾動中止時,是否要執行一次關鍵代碼。true表示執行,false表示不執行。

在上面的節流函數中,由於startTime是在外部函數中初始化的,因此滾動開始時必會執行一次關鍵代碼。

節流函數的用法示例:

var myEfficientFn = throttle(function() {
    // 滾動中的真正想要執行的代碼
    console.log('ok' + new Date());
}, 500,1000,false);
 
// 綁定監聽
window.addEventListener('scroll', myEfficientFn);
//或者這樣,效果是同樣的
window.addEventListener('scroll',throttle(function() {
    // 滾動中的真正想要執行的代碼
    console.log('ok' + new Date());
}, 500,1000,false));

 underscore.js裏封裝的節流函數:

// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
_.throttle = function(func, wait, options) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  if (!options) options = {};
  var later = function() {
    previous = options.leading === false ? 0 : _.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function() {
    var now = _.now();
    if (!previous && options.leading === false) previous = now;
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

 上面的防抖函數和節流函數能夠應用到全部相似scroll事件這種頻繁被觸發的事件的優化,好比resize事件、鍵盤事件、鼠標滾輪事件等。

 

(完)

參考文章:

1.【前端性能】高性能滾動 scroll 及頁面渲染優化

2.函數防抖與節流

3.setTimeout 和 setInterval最小執行時間問題

4.關於setTimeout()你所不知道的地方

相關文章
相關標籤/搜索