requestAnimationFrame的应用

学习vue-element-admin这个项目的table组件的时候,点击分页会主动滚动到最上面,方便查看。以为这个功能挺不错,就去研究了一下它的实现方式。vue

源码位置位于src/utils/scroll-to.js,对外暴露了一个scrollTo方法。git

/** * @param {number} to 滚动到位置 * @param {number} duration 滚动动画持续时间 * @param {Function} callback 滚动结束回调函数 */
export function scrollTo(to, duration, callback) {
  // todo
}
复制代码

requestAnimationFrame

requestAnimationFrame告诉浏览器——你但愿执行一个动画,而且要求浏览器在下次重绘以前调用指定的回调函数更新动画。该方法须要传入一个回调函数做为参数,该回调函数会在浏览器下一次重绘以前执行。而且改回调函数执行此时一般与浏览器屏幕刷新次数相匹配。一般为每秒60次。github

优势:web

在来看标题scroll平滑滚动到指定位置,天然而然就能想到,在requestAnimationFrame的回调的回调函数中一次次的修改一下滚动的位置,就能够实现一个平滑的scroll滚动了。查看了一下element-uiBacktop组件中也是使用这个api来实现的。element-ui

实现思路

Element.scrollTop 属性能够获取或设置一个元素的内容垂直滚动的像素数。api

  • 1.经过scrollTop获取元素垂直滚动的像素值(start)。
  • 2.requestAnimationFrame回调函数的执行次数一般是每秒60次,也就是每17秒左右一次。经过duration(持续时间)和17秒算出整个动画执行次数。
  • 3.经过前两步的数据算出每次向上滚动的距离,经过scrollTop设置元素滚动的像素。
  • 4.在duration结束后,中止requestAnimationFrame回调的执行。

实现

const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
  t /= d / 2;
  if (t < 1) {
    return (c / 2) * t * t + b;
  }
  t--;
  return (-c / 2) * (t * (t - 2) - 1) + b;
};

// 最后面哪一个setTimeout是为了兼容不支持requestAnimationFrame的浏览器。
const requestAnimFrame = (function() {
  return (
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    (window as any).mozRequestAnimationFrame ||
    function(callback) {
      window.setTimeout(callback, 1000 / 60);
    }
  );
})();

// 这里不太好检测滚动是哪一个元素,因此干脆都移动了
function move(amount: number) {
  document.documentElement.scrollTop = amount;
  (document.body.parentNode as HTMLElement).scrollTop = amount;
  document.body.scrollTop = amount;
}
function position() {
  return (
    document.documentElement.scrollTop ||
    (document.body.parentNode as HTMLElement).scrollTop ||
    document.body.scrollTop
  );
}
export function scrollTo(to: number, duration: number, callback?: Function) {
  // 开始滚动的位置
  const start = position();
  // 须要滚动的距离
  const change = to - start;
  // 当前时间
  let currentTime = 0;
  // 每次增加时间
  // 注:vue-element-admin中这个值是20,若是不考虑兼容性,我我的以为应该设置为17毫秒,由于按照requestAnimationFrame回调函数每秒执行60此来算,每次花费16.666666毫秒,猜想设置为20毫秒是为了防止setTimeout出现延时形成问题吧。
  const increment = 20;
  // 持续时间,默认500毫秒
  duration = typeof duration === "undefined" ? 500 : duration;

  const animateScroll = function() {
    currentTime += increment;
    // 计算移动的距离
    const val = easeInOutQuad(currentTime, start, change, duration);
    // 移动
    move(val);

    if (currentTime < duration) {
      // 递归调用
      requestAnimFrame(animateScroll);
    }
  };
  animateScroll();
}

复制代码
相关文章
相关标签/搜索