在绑定 scroll 这类事件时,当它发生时,它被触发的频次非常高,间隔很近。如果事件中涉及到大量的位置计算、DOM 操作、元素重绘等工作且这些工作无法在下一个 scroll 事件触发前完成,就会造成浏览器掉帧。加之用户鼠标滚动往往是连续的,就会持续触发 scroll 事件导致掉帧扩大、浏览器 CPU 使用率增加、用户体验受到影响。
优化方法
1、防抖(debounce)
防抖技术可以把多个顺序地调用合并成一次,即在规定的时间间隔内只执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function debounce(func, wait, immediate) { var timeout; return function() { clearTimeout(timeout); timeout = setTimeout(func, wait); }; }; function realFunc(){ console.log("Success"); } window.addEventListener('scroll',debounce(realFunc,500)); window.addEventListener('scroll',realFunc);
|
2、节流(throttle)
节流技术只允许一个函数在 x 毫秒内执行一次,跟防抖技术主要的不同在于,保证 x 毫秒内至少执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| var timeout, startTime = new Date(); return function() { var context = this, args = arguments, curTime = new Date(); clearTimeout(timeout); if(curTime - startTime >= mustRun){ func.apply(context,args); startTime = curTime; }else{ timeout = setTimeout(func, wait); } }; }; function realFunc(){ console.log("Success"); } window.addEventListener('scroll',throttle(realFunc,500,1000));
|
3、requestAnimationFrame实现节流方法
requestAnimationFrame 可替代 throttle,函数需要重新计算和渲染屏幕上的元素时,想保证动画或变化的平滑性,可以用它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var ticking = false; function onScroll(){ if(!ticking) { requestAnimationFrame(realFunc); ticking = true; } } function realFunc(){ console.log("Success"); ticking = false; } window.addEventListener('scroll', onScroll, false);
|
详见:
高性能滚动 scroll 及页面渲染优化
实例解析防抖和节流函数
页面高性能滚动scroll优化——防抖与节流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| function debounce(fn, wait) { var timmer return function() { if (timmer) { clearTimeout(timmer) } timmer = setTimeout(fn, wait) } } function throttle(fn, delay) { let timer = null; return function() { if(timer) return false timer = setTimeout(() => { fn() timer = null }, delay) } } function throttle(func, wait) { var args, result, thisArg, timeoutId, lastCalled = 0; return function() { var now = new Date, remain = wait - (now - lastCalled); args = arguments; thisArg = this; if (remain <= 0) { lastCalled = now; result = func.apply(thisArg, args); } else if (!timeoutId) { timeoutId = setTimeout(() => { lastCalled = new Date; timeoutId = null; result = func.apply(thisArg, args); }, remain); } return result; }; }
|