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();
}

複製代碼
相關文章
相關標籤/搜索