現在很多功能都需要監聽onscroll事件完成的,然而onscroll觸發得很頻繁也是衆所周知。
測試一下(請先讓瀏覽器出現滾動條)
var no = 0;
/**
* 統計觸發次數
*/
function statistical() {
console.log( ++no );
}
window.addEventListener('scroll', function() {
statistical();
});
減壓方式
1.setTimeout、clearTimeout
var no = 0,
timeout;
/**
* 統計觸發次數
*/
function statistical() {
console.log( ++no );
}
window.addEventListener('scroll',function() {
if ( timeout ) {
clearTimeout( timeout );
}
timeout = setTimeout(function() {
statistical();
timeout = null;
},300);
});
弊端
onScroll觸發多少次,就會執行多少次clearTimeout、setTimeout,
一直滑動滑輪的時候,不會觸發處理函數,因爲onScroll一直在clearTimeout。
2.setInterval
var change = false,
no = 0;
/**
* 統計觸發次數
*/
function statistical() {
console.log( ++no );
}
/**
* 間隔一定時間判斷滾動座標是否變化,變化則執行處理
*/
setInterval(function() {
if ( change ) {
change = false;
statistical();
}
},300);
/**
* 監聽onScroll事件,告訴setInterval,滾動座標已經發生改變
*/
window.addEventListener('scroll',function() {
change = true;
});
弊端
setInterval一直佔用內存。
總結
setTimeout的方式clearTimeout、setTimeout太頻繁,壓力還是相當大;
而setInterval的方式,setInterval一直佔用內存,但大多數的情況下,它只做判斷,就是判斷change是否爲true。
onscroll真的只做一件事,就是設置change變量爲true,壓力少了很多。
由此可見,setInterval 優於 setTimeout。
但我們總不能一個處理,添加一個onScroll、一個setInterval呢,所以寫成模塊。
模塊代碼
!(function( obj ) {
//異步執行,有更好的方法,詳見http://blog.csdn.net/zakkye/article/details/45890829
var asynFire = asynFire || function(handle) { setTimeout.call( window, handle ) };
//eventId前綴,是個隨機字符串,避免一個頁面存在兩個 scrollListener 的時候,eventId相同
var idPf = Math.random().toString(36).substr(2).substr(0,4) + '_',
//累加Id
guid = 1,
//window 當前的scrollLeft
nX = 0,
//window 當前的scrollTop
nY = 0,
//setInterval Id
interval = 0,
//scrollListener 監聽事件所用id
eventId = '.scrollListener',
//是否已初始化
inited = false,
//setInterval 延遲時間
lazy = 300,
//所有回調緩存對象
callbacks = {},
//滾動座標是否已改變
change = false;
/**
* 初始化,對基本屬性賦值及添加監聽
*/
function init() {
//已經初始化就不操作
if ( inited ) return;
addEvent();
addInterval();
inited = true;
}
/**
* 添加scroll事件監聽
*/
function addEvent() {
$(window).on('scroll' + eventId,function(e){
change = true;
});
}
/**
* 刪除scroll事件監聽
*/
function removeEvent () {
$(window).off(eventId);
}
/**
* 添加循環監聽
*/
function addInterval () {
interval = setInterval(function(){
determine();
}, lazy);
}
/**
* 刪除循環監聽
*/
function removeInterval () {
clearInterval( interval );
interval = null;
}
/**
* 判斷當時是否應該執行回調
*/
function determine() {
var $window,
_oX,
_oY,
_nX,
_nY;
if ( change ) {
change = false;
$window = $(window);
_oX = nX;
_oY = nY;
_nX = $window.scrollLeft();
_nY = $window.scrollTop();
scrollListener.fire( _nX, _nY , _nX > _oX, _nY > _oY );
nX = _nX;
nY = _nY;
}
}
/**
* 滾動監聽功能(減少onscroll處理壓力)
* @type {Object}
*/
var scrollListener = {
/**
* add 添加處理
* @param {Function} callback
* @return {boolean || number} 添加成功返回對應id,否則返回false
*/
add : function( callback ) {
var id,
$window;
if ( typeof callback === 'function' ) {
$window = $( window );
if ( !inited ) {
init();
}
id = idPf + ( guid++ );
callbacks[id] = callback;
//由於會先執行一次,所以必須先返回eventId,再執行,所以用了異步,而且事件監聽本來也屬於異步
//也存在一些函數,判斷頁面已經滾動到相應區域,執行一次處理後,就要scrollListener.remove了,所以必須異步
asynFire(function() {
callback({
x : $window.scrollLeft(),
y : $window.scrollTop(),
right : true,
down : true
})
});
return id;
}
return false;
},
/**
* 刪除處理
* @param {number} id 添加回調時返回的id
* @return {boolean} 是否刪除成功
*/
remove : function( id ) {
if (id && callbacks[id]) {
delete callbacks[id];
return true;
}
return false;
},
/**
* 執行所有回調
* @param {number} x 滾動條x座標
* @param {number} y 滾動條y座標
* @param {boolean} right 是否爲向右滾動
* @param {boolean} down 是否爲向下滾動
* @return {scrollListener}
*/
fire : function( x, y, right, down ) {
var i,
data = {
x : typeof x === 'number' ? x : $(window).scrollLeft(),
y : typeof y === 'number' ? y : $(window).scrollTop(),
right : typeof right === 'boolean' ? right : true,
down : typeof down === 'boolean' ? down : true
};
for (i in callbacks) {
callbacks[i]( data );
}
return this;
},
/**
* 設置監聽延遲時間
* @param {number} time 時間戳
* @return {scrollListener}
*/
setLazy : function( time ) {
if ( typeof time === 'number' ) {
//如果已經初始化,則馬上修改監聽時間
lazy = time;
if ( inited ) {
removeInterval();
addInterval();
}
}
return this;
},
/**
* 銷燬
* @return {scrollListener}
*/
destroy : function () {
removeEvent()
removeInterval();
inited = false;
callbacks = {};
return this;
}
}
obj.scrollListener = scrollListener;
})( window );
測試例子
var id1 = scrollListener.add(function() {
console.log('scroll1');
});
var id2 = scrollListener.add(function() {
console.log('scroll2');
});
scrollListener.remove( id2 );