浏览器高性能滑动解决方案
vinsonli
8年前
<p>最近一段时间,在项目中实现过瀑布流、懒加载、侧边栏导航等功能。总觉得在处理浏览器scroll滑动的时候有点问题,通过计算各个模块的高度和某些指定模块的出现时机时,在PC端还好,在移动端容易出现卡顿和抖动的情况。特此整理解决方案。</p> <h2>在停止滑动后执行</h2> <p>如果我们需要在滑动的时候进行某些操作,可以在停止滑动后再延迟进行,这样就不会一边滑动一边执行了。</p> <pre> <code class="language-javascript">//滑动停止后延迟wait毫秒后才执行func function debounce(func, wait) { // 定时器变量 var timeout; return function() { // 每次触发 scroll handler 时先清除定时器 clearTimeout(timeout); // 指定 xx ms 后触发真正想进行的操作 handler timeout = setTimeout(func, wait); }; }; // 实际想绑定在 scroll 事件上的处理函数 function realFunc(){ console.log("Success"); } // 采用了防抖动 window.addEventListener('scroll',debounce(realFunc,500)); // 没采用防抖动 //window.addEventListener('scroll',realFunc); </code></pre> <p>还可以更好的封装一番:</p> <pre> <code class="language-javascript">// 防抖动函数 function debounce(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate & !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; }; var myEfficientFn = debounce(function() { // 滚动中的真正的操作 }, 250); // 绑定监听 window.addEventListener('scroll', myEfficientFn); </code></pre> <h2>节流,在滑动中间隔执行</h2> <p>如果我们不是在滑动停止后执行,而是在滑动中需要实时计算一些东西,就可以采用 <strong>节流</strong> 的方式。</p> <p>节流函数,只允许一个函数在 X 毫秒内执行一次。</p> <pre> <code class="language-javascript">// 简单的节流函数 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)); </code></pre> <p>如果在一段时间内 scroll 触发的间隔一直短于 500ms ,那么能保证事件我们希望调用的 handler 至少在 1000ms 内会触发一次。</p> <h2>高精度控制</h2> <p>上面的两种方式都是通过setTimeout来实现的,精度不够高,如果对浏览器兼容性要求不高,或者是移动端web,可以使用原生的 requestAnimationFrame 来实现。</p> <p>该方法的原理: 在浏览器的页面重绘之前,通知浏览器调用一个指定的函数。该方法被调用的频率为每秒60次 。</p> <p>所以说用该方法来触发滚动事件,相当于上面的:</p> <pre> <code class="language-javascript">throttle(func, xx, 1000/60) //xx 代表 xx ms内不会重复触发事件 handler </code></pre> <pre> <code class="language-javascript">var ticking = false; // rAF 触发锁 function onScroll(){ if(!ticking) { requestAnimationFrame(realFunc); ticking = true; } } function realFunc(){ // do something... console.log("Success"); ticking = false; } // 滚动事件监听 window.addEventListener('scroll', onScroll, false); </code></pre> <p>这样浏览器就会以16.7ms的频率触发事件。</p> <p>至于移动端,最好还是使用iscroll这样的模拟事件滑动的库来解决其延迟问题吧。</p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959674300079358219" rel="nofollow">http://brizer.github.io/2016/06/11/浏览器高性能滑动解决方案/</a></p> <p> </p>