scrollTop的兼容性
scroll事件,當用戶滾動帶滾動條的元素中的內容時,在該元素上面觸發。<body>元素中包含所加載頁面的滾動條。
雖然scroll事件是在window對象上發生,但他實際表示的則是頁面中相應元素的變化。在混雜模式(document.compatMode的值為BackCompat)下,可以通過<body>元素的scrollLeft和scrollTop來監控到這一變化。
而在標准模式(document.compatMode的值為CSS1Compat)下,除Safari之外的所有瀏覽器都會通過<html>元素來反應這一變化。
以上內容來自《Javascript 高級程序設計(第三版)》。
以下是我自己測試的結果,截止2017-05-18,用的是最新版的chrome、Firefox和Win7中的IE。
- 混雜模式下,chrome、IE、Firefox都是通過document.body.scrollTop監聽滾動條的位置。
- 標准模式下,chrome通過document.body.scrollTop監聽滾動條位置,IE和Firefox通過document.documentElement.scrollTop監聽滾動條位置
可以用下面的代碼進行驗證:
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操作,就會大大增加瀏覽器的負擔,消耗性能。
通過看別人的文章,知道了可以通過防抖函數和節流函數對這種頻繁觸發的事件進行優化。
防抖函數達成的效果是: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事件、鍵盤事件、鼠標滾輪事件等。
(完)
參考文章:
2.函數防抖與節流
