如何使用基础 JavaScript 创建滚动触发动画

Avatar of Md Shuvo
Md Shuvo

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

网站上的一点动画可以增添一些趣味,给用户留下深刻印象并吸引他们的注意力。您可以让它们在页面加载时立即运行,无论它们在页面上的哪个位置。但是,如果您的网站很长,用户需要花费一些时间才能向下滚动到该元素怎么办?他们可能会错过它。

您可以让它们一直运行,但也许动画的设计最佳方式是确保您能看到它的开始部分。诀窍是在用户向下滚动到该元素时开始动画——也就是滚动触发动画。

为了解决这个问题,我们使用 **滚动触发器**。当用户向下滚动到任何特定元素时,我们可以使用该事件来执行某些操作。它可以是任何操作,甚至可以是动画的开始。它甚至可以是图像的滚动触发延迟加载或整个评论部分的延迟加载。这样,我们不会强迫用户在初始页面加载时下载视口中不存在的元素。许多用户可能根本不会向下滚动,因此我们确实为他们(以及我们自己)节省了带宽和加载时间。

滚动触发器非常有用。有很多库可用于实现它们,例如 Greensock 的流行 ScrollTrigger 插件。但您不必使用第三方库,尤其是在处理相当简单的想法时。事实上,您只需使用少量原生 JavaScript 就可以自己实现它。这就是我们将在本文中要做的。

以下是我们如何创建滚动触发事件

  • 创建一个名为 scrollTrigger 的函数,我们可以将其应用于某些元素
  • 当元素进入视口时,在元素上应用 .active
  • 使用 CSS 对 .active 类进行动画

有时添加 .active 类是不够的。例如,我们可能希望改为执行自定义函数。这意味着我们应该能够传递一个在元素可见时执行的自定义函数。像这样

scrollTrigger('.loader', {
  cb: function(el) {
    el.innerText = 'Loading ...'
    loadContent()
  }
})

我们还将尝试处理旧版不支持浏览器的滚动触发器。

但首先,IntersectionObserver API

我们将要使用的主要 JavaScript 功能是 Intersection Observer。此 API 提供了一种以 *异步* 方式观察目标元素交集变化的方法——并且它比监视 scroll 事件的方式更有效率。我们将使用 IntersectionObserver 来监视滚动何时到达页面上某些元素可见的点。

让我们开始构建滚动触发器

我们想创建一个名为 scrollTrigger 的函数,并且此函数应将选择器作为其参数。

function scrollTrigger(selector) {
  // Multiple element can have same class/selector,
  // so we are using querySelectorAll
  let els = document.querySelectorAll(selector)
  // The above `querySelectorAll` returns a nodeList,
  // so we are converting it to an array
  els = Array.from(els)
  // Now we are iterating over the elements array
  els.forEach(el => {
    // `addObserver function` will attach the IntersectionObserver to the element
    // We will create this function next
    addObserver(el)
  })
}
// Example usage
scrollTrigger('.scroll-reveal')

现在让我们创建 addObserver 函数,我们希望使用 IntersectionObserver 附加到元素上

function scrollTrigger(selector){
  let els = document.querySelectorAll(selector)
  els = Array.from(els)
  els.forEach(el => {
    addObserver(el)
  })
}
function addObserver(el){
    // We are creating a new IntersectionObserver instance
    let observer = new IntersectionObserver((entries, observer) => { // This takes a callback function that receives two arguments: the elements list and the observer instance.
      entries.forEach(entry => {
        // `entry.isIntersecting` will be true if the element is visible
      if(entry.isIntersecting) {
        entry.target.classList.add('active')
        // We are removing the observer from the element after adding the active class
        observer.unobserve(entry.target)
      }
    })
  })
  // Adding the observer to the element
  observer.observe(el)
}
// Example usage
scrollTrigger('.scroll-reveal')

如果我们执行此操作并滚动到具有 .scroll-reveal 类的元素,则会将 .active 类添加到该元素。但请注意,active 类会在元素的任何一小部分可见时立即添加。

带有代码的左侧滚动触发动画和右侧打开的 DevTools。

但这可能有点过头了。相反,我们可能希望在元素的 *更大* 部分可见时添加 .active 类。幸运的是,IntersectionObserver 接受一些用于此目的的选项 作为其第二个参数。让我们将其应用于我们的 scrollTrigger 函数

// Receiving options as an object
// If the user doesn't pass any options, the default will be `{}`
function scrollTrigger(selector, options = {}) {
  let els = document.querySelectorAll(selector)
  els = Array.from(els)
  els.forEach(el => {
    // Passing the options object to the addObserver function
    addObserver(el, options)
  })
}
// Receiving options passed from the scrollTrigger function
function addObserver(el, options) {
  let observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if(entry.isIntersecting) {
        entry.target.classList.add('active')
        observer.unobserve(entry.target)
      }
    })
  }, options) // Passing the options object to the observer
  observer.observe(el)
}
// Example usage 1:
// scrollTrigger('.scroll-reveal')
// Example usage 2:
scrollTrigger('.scroll-reveal', {
  rootMargin: '-200px'
})

就这样,我们的前两个议程项目完成了!

让我们继续第三个项目——在滚动到目标元素时添加执行回调函数的功能。具体来说,让我们在选项对象中将回调函数作为 cb 传递

function scrollTrigger(selector, options = {}) {
  let els = document.querySelectorAll(selector)
  els = Array.from(els)
  els.forEach(el => {
    addObserver(el, options)
  })
}
function addObserver(el, options){
  let observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if(entry.isIntersecting){
        if(options.cb) {
          // If we've passed a callback function, we'll call it
          options.cb(el)
        } else{
          // If we haven't, we'll just add the active class
          entry.target.classList.add('active')
        }
        observer.unobserve(entry.target)
      }
    })
  }, options)
  observer.observe(el)
}
// Example usage:
scrollTrigger('.loader', {
  rootMargin: '-200px',
  cb: function(el){
    el.innerText = 'Loading...'
    // Done loading
    setTimeout(() => {
      el.innerText = 'Task Complete!'
    }, 1000)
  }
})
An updated animated screenshot of the same scroll-triggered animation. As boxes enter the screen from the bottom, a they rotate. A "loading" message that changes to "finished loading" message is the last element to scroll into view. The code is open to the left of the animation.

太棒了!还有一件事我们需要处理:旧版浏览器支持。某些浏览器可能缺乏对 IntersectionObserver 的支持,因此让我们在 addObserver 函数中处理这种情况

function scrollTrigger(selector, options = {}) {
  let els = document.querySelectorAll(selector)
  els = Array.from(els)
  els.forEach(el => {
    addObserver(el, options)
  })
}
function addObserver(el, options) {
  // Check if `IntersectionObserver` is supported
  if(!('IntersectionObserver' in window)) {
    // Simple fallback
    // The animation/callback will be called immediately so
    // the scroll animation doesn't happen on unsupported browsers
    if(options.cb){
      options.cb(el)
    } else{
      entry.target.classList.add('active')
    }
    // We don't need to execute the rest of the code
    return
  }
  let observer = new IntersectionObserver((entries, observer) =>; {
    entries.forEach(entry => {
      if(entry.isIntersecting) {
        if(options.cb) {
          options.cb(el)
        } else{
          entry.target.classList.add('active')
        }
        observer.unobserve(entry.target)
      }
    })
  }, options)
  observer.observe(el)
}
// Example usages:
scrollTrigger('.intro-text')
scrollTrigger('.scroll-reveal', {
  rootMargin: '-200px',
})
scrollTrigger('.loader', {
  rootMargin: '-200px',
  cb: function(el){
    el.innerText = 'Loading...'
    setTimeout(() => {
      el.innerText = 'Task Complete!'
    }, 1000)
  }
})

这是那个实时演示

这次小小的旅程就到这里了!希望您喜欢它并在过程中学到了一些新东西。