Sal is a performance-centric, lightweight scrolling animation library

1. Introduction

Sal (Scroll extension Library) provides a high performance and lightweight solution for scroll animation. Sal uses the Intersection Observer, which provides good performance in examining elements in the viewport. It is strongly recommended to read the article on the use of IntersectionObserver API by Ruan Da-shen to understand the use of basic IntersectionObserver

This reading is divided into five parts, respectively for the introduction, use, analysis, demo, summary, five parts are not connected to each other can be separated according to the need to see.

1 introduction for the introduction, 2 use for the use of the library, 3 analysis for the source code of the analysis, 4Demo is the core of the source code of the small demo, 5 summary blowing water, learn to apply.

It is recommended to read with the source code combined with this article, so that it is easier to understand!

  1. IntersectionObserver API usage tutorial
  2. sal
  3. Sal resolves the Github address

2. Use


      
<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>
Copy the code

When the page starts scrolling, the tag with the data-Sal attribute added to it will be animated as it scrolls.

Data-sal has three options:

  • data-sal-duration– Animation duration;
  • data-sal-delay– Animation delay time;
  • data-sal-easing– Animation speed curve.

The sal function takes three arguments:

  • threshold– Visible scale of the target element
  • once– Perform only one animation
  • disable– Disable animation

3. The parsing

The principle of the library is to observe the visibility ratio of target elements through the API of IntersectionObserver and start animation by adding or removing classes

import './sal.scss';

/** * Default option */
let options = {
  rootMargin: '0% 50%'.threshold: 0.5.animateClassName: 'sal-animate'.disabledClassName: 'sal-disabled'.selector: '[data-sal]'.once: true.disabled: false};/** * private */
let elements = [];
let intersectionObserver = null;

/** * Add class startup animation to element * @param {Node} element */
const animate = element= > (
  element.classList.add(options.animateClassName)
);

/** * Reverse the startup animation by removing the class * @param {Node} element */
const reverse = element= > (
  element.classList.remove(options.animateClassName)
);

/** * Whether the element has been animated * @param {Node} element */
const isAnimated = element= > (
  element.classList.contains(options.animateClassName)
);

/** * Remove disabledClassName for the element to enable animation */
const enableAnimations = (a)= > {
  document.body.classList.remove(options.disabledClassName);
};

/** * Disable animation by adding class */
const disableAnimations = (a)= > {
  document.body.classList.add(options.disabledClassName);
};

/** * Whether to disable animation * @return {Boolean} */
const isDisabled = (a)= > (
  options.disabled ||
  (
    (typeof options.disabled === 'function') &&
    options.disabled()
  )
);

/ IntersectionObserver callback function * * * * @ param {Array < IntersectionObserverEntry >} entries * @ param {IntersectionObserver} observer */
const onIntersection = (entries, observer) = > {
  entries.forEach((entry) = > {
    if (entry.intersectionRatio >= options.threshold) {
      // The visible ratio of the element is greater than the configured visible ratio
      animate(entry.target);

      if(options.once) { observer.unobserve(entry.target); }}else if(! options.once) {// Otherwise, start the reverse animationreverse(entry.target); }}); };/** * disable sal */
const disable = (a)= > {
  disableAnimations();

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

/** * start */
const enable = (a)= > {
  enableAnimations();

  /** * Sets the behavior function * intersectionObserver: observer * onIntersection: the behavior function to observe the change */
  intersectionObserver = new IntersectionObserver(onIntersection, {
    rootMargin: options.rootMargin,
    threshold: options.threshold,
  });

  // Get the observation element
  elements = [].filter.call(
    document.querySelectorAll(options.selector), element => ! isAnimated(element, options.animateClassName), );// Sets the observer for the observed element, firing the behavior function when the change occurs
  elements.forEach(element= > intersectionObserver.observe(element));
};

/** * Init * @param {Object} settings * @return {Object} public API */
const init = (settings = options) = > {
  // Initialize the configuration
  if(settings ! == options) { options = { ... options, ... settings, }; }// Check whether the browser has 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 `);
  }

  // Start and end animations
  if(! isDisabled()) { enable(); }else {
    disableAnimations();
  }

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

export default init;
Copy the code

4.demo

By implementing Ruan Da-shen’s two examples, we can start IntersectionObserver, which is also the principle of SAL

4.1 Lazy Load

When scrolling to a certain position, then load the corresponding image


      
<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>
Copy the code

4.2 Infinite Scroll

Watch for more elements to load at the bottom of the list, and load data into the list every time it reaches a set visibility ratio


      
<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">To load more</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>
Copy the code

5. To summarize

Sal is mainly applied to IntersectionObserver with a simple code of only over 100 lines. However, as IntersectionObserver is only an EXPERIMENTAL API (although Chrome supports it), it is not likely to be used in practical projects. But look forward to it. For example, if unlimited scrolling is not used, IntersectionObserver must monitor browser scrolling events, obtain list height, window height and scroll height to calculate whether scrolling to the bottom, and anti-jitter should be added if necessary to optimize user experience. So IntersectionObserver still saves a lot of steps, so watch it!

Fast forward to 2019, keep sharing your output!