sal源碼解析-輕量級的滾動動畫庫

sal是以性能爲中心,輕量級的滾動動畫庫javascript

1.前言

sal(滾動擴展庫)爲滾動動畫提供高性能和輕量級的解決方案。sal採用Intersection Observer,在視口中,它在檢查元素方面提供了很好的性能。強烈建議優先閱讀阮大神的IntersectionObserver API 使用教程文章,瞭解基本IntersectionObserver的使用css

本篇讀後感分爲五部分,分別爲前言、使用、解析、demo、總結,五部分互不相連可根據須要分開看。html

1前言爲介紹、2使用爲庫的使用、3解析爲源碼的解析、4demo是抽取源碼的核心實現的小demo,5總結爲吹水,學以至用。java

建議跟着源碼結合本文閱讀,這樣更加容易理解!git

  1. IntersectionObserver API 使用教程
  2. sal
  3. sal解析的Github地址

2.使用

<!DOCTYPE html>
<html lang="en">
<body>
  <div data-sal="slide-up" data-sal-delay="300" data-sal-easing="ease-out-bounce" ></div>
</body>
<script> sal({ once: false }); </script>
</html>
複製代碼

當頁面開始滾動時,爲標籤添加了data-sal屬性的標籤就會隨着滾動展現動畫效果。github

data-sal有三種選項:chrome

  • data-sal-duration - 動畫時長;
  • data-sal-delay - 動畫延遲時間;
  • data-sal-easing - 動畫速度曲線。

sal函數接收三個參數:api

  • threshold- 目標元素的可見比例
  • once - 只執行一次動畫
  • disable - 禁用動畫

3.解析

庫的原理是經過IntersectionObserverapi,觀察目標元素的可見比例,經過添加或者移除class來啓動動畫瀏覽器

import './sal.scss';

/** * 默認選項 */
let options = {
  rootMargin: '0% 50%',
  threshold: 0.5,
  animateClassName: 'sal-animate',
  disabledClassName: 'sal-disabled',
  selector: '[data-sal]',
  once: true,
  disabled: false,
};

/** * 私有 */
let elements = [];
let intersectionObserver = null;

/** * 爲元素添加class啓動動畫 * @param {Node} element */
const animate = element => (
  element.classList.add(options.animateClassName)
);

/** * 經過移除class來反轉啓動動畫 * @param {Node} element */
const reverse = element => (
  element.classList.remove(options.animateClassName)
);

/** * 元素是否已經啓動過動畫 * @param {Node} element */
const isAnimated = element => (
  element.classList.contains(options.animateClassName)
);

/** * 爲元素移除disabledClassName來啓用動畫 */
const enableAnimations = () => {
  document.body.classList.remove(options.disabledClassName);
};

/** * 經過添加class來禁用動畫 */
const disableAnimations = () => {
  document.body.classList.add(options.disabledClassName);
};

/** * 是否禁用動畫 * @return {Boolean} */
const isDisabled = () => (
  options.disabled ||
  (
    (typeof options.disabled === 'function') &&
    options.disabled()
  )
);

/** * IntersectionObserver的回調函數 * @param {Array<IntersectionObserverEntry>} entries * @param {IntersectionObserver} observer */
const onIntersection = (entries, observer) => {
  entries.forEach((entry) => {
    if (entry.intersectionRatio >= options.threshold) {
      // 元素的可見比例大於配置的可見比例,啓動動畫
      animate(entry.target);

      if (options.once) {
        observer.unobserve(entry.target);
      }
    } else if (!options.once) {
      // 不然,啓動反轉動畫
      reverse(entry.target);
    }
  });
};

/** * 禁用sal */
const disable = () => {
  disableAnimations();

  intersectionObserver.disconnect();
  intersectionObserver = null;
};

/** * 啓動 */
const enable = () => {
  enableAnimations();

  /** * 設置對觀察元素變化後的行爲函數 * intersectionObserver:觀察者 * onIntersection:觀察到變化的行爲函數 */
  intersectionObserver = new IntersectionObserver(onIntersection, {
    rootMargin: options.rootMargin,
    threshold: options.threshold,
  });

  // 獲取觀察元素
  elements = [].filter.call(
    document.querySelectorAll(options.selector),
    element => !isAnimated(element, options.animateClassName),
  );

  // 爲觀察元素設置觀察者,當變化後觸發行爲函數
  elements.forEach(element => intersectionObserver.observe(element));
};

/** * Init * @param {Object} settings * @return {Object} public API */
const init = (settings = options) => {
  // 初始化配置
  if (settings !== options) {
    options = {
      ...options,
      ...settings,
    };
  }

  // 判斷瀏覽器是否存在IntersectionObserver
  if (!window.IntersectionObserver) {
    disableAnimations();

    throw Error(` Your browser does not support IntersectionObserver! Get a polyfill from here: https://github.com/w3c/IntersectionObserver/tree/master/polyfill `);
  }

  // 開始和結束動畫
  if (!isDisabled()) {
    enable();
  } else {
    disableAnimations();
  }

  return {
    elements,
    disable,
    enable,
  };
};

export default init;
複製代碼

4.demo

經過實現阮大神的兩個例子來上手IntersectionObserver,也是sal的原理app

4.1 惰性加載(lazy load)

當滾動到必定位置的時候,再加載對應的圖片

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>lazyLoad</title>
  <style> html, body{ height: 100%; padding: 0; margin: 0; } .block{ width: 100%; height: 700px; } .red{ background-color: red; } .green{ background-color: green; } .yellow{ background-color: yellow; } img{ width: 100%; } </style>
</head>
<body>
  <div class="block red"></div>
  <div class="block green"></div>
  <div class="block yellow"></div>
</body>
<script> var threshold = 0.3 var onIntersection = (changes, observer) => { changes.forEach(function(change) { var container = change.target if (change.intersectionRatio > threshold) { var img = new Image() img.src = './fafa.jpeg' container.append(img) observer.unobserve(container) } }) } var observer = new IntersectionObserver(onIntersection, {threshold}) document.querySelectorAll('.block').forEach(element => observer.observe(element)) </script>
</html>
複製代碼

4.2 無限滾動(infinite scroll)

觀察列表底部元素加載更多,每當達到設定的可見比例時,就加載數據到列表中

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>lazyLoad</title>
  <style> html, body{ height: 100%; padding: 0; margin: 0; } h1{ border-bottom: 1px solid #000; } </style>
</head>
<body>
  <div class="wrap">
    <div class="list"></div>
    <div class="bottom">加載更多</div>
  </div>
</body>
<script> var num = 0 var skip = 10 var threshold = 0.9 function load(){ var list = document.querySelector('.list') var fragment = document.createDocumentFragment(); Array(skip).fill().forEach((v, i) => { var dom = document.createElement('h1') num += 1 dom.innerText = num fragment.append(dom) }) list.append(fragment) } var onIntersection = (changes, observer) => { changes.forEach(function(change) { if (change.intersectionRatio > threshold) load() }) } var observer = new IntersectionObserver(onIntersection, {threshold}) observer.observe(document.querySelector('.bottom')) </script>
</html>
複製代碼

5.總結

sal這個庫其實主要是對IntersectionObserver的應用,代碼簡單僅僅只有一百多行,但因爲IntersectionObserver還只是個實驗階段的api(雖然chrome支持了),在實際項目中運用的機會不是太大,可是對它抱有期待。就如無限滾動的例子,若是不使用IntersectionObserver的話,就得監聽瀏覽器滾動事件,獲取列表高度、窗口高度和滾動高度來計算是否滾動到底部,必要狀況下還須要加上防抖動來優化用戶體驗,因此IntersectionObserver仍是省去不少步驟的,看好!

轉眼就到了2019年了,要堅持分享輸出!

相關文章
相關標籤/搜索