当用户将元素滚动到视口时触发一个函数 – Vanilla JavaScript

Trigger a function when the user scrolls the element into the viewport – Vanilla JavaScript

我写了一个函数,它可以使条形图动画化。目前这个动作是在页面加载时触发的,但我只希望它在我到达元素时触发——否则用户将看不到动画。 我想通过 vanilla JavaScript 实现这一点,有可能吗?

这是我的标记:

<div class="section">
  section
</div>
<div class="section">
  section
</div>
<div class="section">
  section
</div>
<div class="section">
  section
  <ul class="skills__list">
    <li class="skills__list-item">
      <div class="bar">
        <span>HTML</span>
        <div class="bar__inner" data-percent="90%"></div>
      </div>
    </li>
    <li class="skills__list-item">
      <div class="bar">
        <span>css</span>
        <div class="bar__inner" data-percent="80%"></div>
      </div>
    </li>
    <li class="skills__list-item">
      <div class="bar">
        <span>Javascript</span>
        <div class="bar__inner" data-percent="60%"></div>
      </div>
    </li>
    <li class="skills__list-item">
      <div class="bar">
        <span>UI design</span>
        <div class="bar__inner" data-percent="70%"></div>
      </div>
    </li>
    <li class="skills__list-item">
      <div class="bar">
        <span>sketch</span>
        <div class="bar__inner" data-percent="50%"></div>
      </div>
    </li>
    <li class="skills__list-item">
      <div class="bar">
        <span>Photoshop</span>
        <div class="bar__inner" data-percent="80%"></div>
      </div>
    </li>
    <li class="skills__list-item">
      <div class="bar">
        <span>Illustrator</span>
        <div class="bar__inner" data-percent="90%"></div>
      </div>
    </li>
  </ul>
</div>

这是 Scss:

*{
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

.section {
  padding: 20px;
  font-size: 30px;
  font-family: sans-serif;
  text-transform: uppercase;
  height: 400px;
  &:nth-child(1){
    background-color: #ddd;
  }
  &:nth-child(2){
    background-color: #aaa;
  }
  &:nth-child(3){
    background-color: #bbb;
  }
  &:nth-child(4){
    background-color: #000;
  }
}

.skills__list-item {
  & + .skills__list-item {
    margin-top: 20px;
  }
}

// bar chart styles
.bar {
  position: relative;
  width: 100%;
  height: 28px;
  overflow: hidden;
  background-color: blue;

  span {
    position: absolute;
    z-index: 9;
    display: flex;
    align-items: center;
    height: 100%;
    padding: 10px;
    color: #fff;
    background-color: red;
  }

  &__inner {
    display: flex;
    justify-content: flex-end;
    width: 0;
    height: 100%;
    background-color: green;
    transform-origin: 0 100%;
    transition: width 0.6s linear;

    &::after {
      content: attr(data-percent);
      align-self: center;
      padding-right: 20px;
      color: #fff;
    }
  }
}

这里是 JavaScript:

const bars = document.querySelectorAll('.bar__inner');


Array.from(bars).forEach((bar, index) => {
  setTimeout(() => {
    const eachBar = bar;
    eachBar.style.width = bar.dataset.percent;
  }, index * 400);
});

这是一个工作示例 Codepen

您可以关注一个 very helpful tip of the website Gomakethings.com

这表明您可以使用getBoundingClientRect()方法来实现您的目标:

// Get the an HTML element
var element = document.querySelector('<a selector>');

// Get its bounding client rectangle
var bounding = element.getBoundingClientRect();

使用它来构建一个函数,通过检索边界框来检查元素是否在视口客户端中(好吧,代码可以改进,这只是一个演示):

function isInViewPort(element) {
    // Get the bounding client rectangle position in the viewport
    var bounding = element.getBoundingClientRect();

    // Checking part. Here the code checks if it's *fully* visible
    // Edit this part if you just want a partial visibility
    if (
        bounding.top >= 0 &&
        bounding.left >= 0 &&
        bounding.right <= (window.innerWidth || document.documentElement.clientWidth) &&
        bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight)
    ) {
        console.log('In the viewport! :)');
        return true;
    } else {
        console.log('Not in the viewport. :(');
        return false;
    }
}

最后,在调用上述函数的scroll事件上添加一个事件监听器

window.addEventListener('scroll', function (event) {
    if (isInViewport(theElementToWatch)) {
      // update the element display
    }
}, false);

根据浏览器兼容性 displayed by the MDN pagegetBoundingClientRect() 完全支持 Chrome 和 Firefox (>= 3.5)。此处提供的解决方案已被最常用的浏览器完全支持(某些移动版本未知)。

根据 Can I use... 网站提供的信息,移动浏览器(Chrome、Firefox 等)完全支持该方法,至少从给定的版本开始是这样。

最后,您可以记住一个仍处于实验阶段的解决方案,旨在取代 getBoundingClientRect() 方法的使用,即 Intersection Observer API(见 Tom M 的回答)。 The MDN related page 解释原因:

Implementing intersection detection in the past involved event handlers and loops calling methods like Element.getBoundingClientRect() to build up the needed information for every element affected. Since all this code runs on the main thread, even one of these can cause performance problems. When a site is loaded with these tests, things can get downright ugly.

您可以为此使用 IntersectionObserver API。

const observer = new IntersectionObserver((entries) => { 
    entries.forEach((entry) => {
        console.log(entry.target);
        // your callback here
    })
}); 

observer.observe(document.querySelector('.skills__list-item'));

不过请注意 Browser support,例如 IE、Safari 和 Opera Mini 不支持它。

如果你想支持 IE7+,你可以使用 the polyfill provided by the W3C

资源