当用户将元素滚动到视口中时触发函数 - 纯JavaScript

8

我编写了一个函数来使柱状图具有动画效果。目前这个动作是在页面加载时触发的,但我只想在到达元素时触发它,否则用户将看不到动画。

我想通过原生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


2
您的意思是当用户滚动时将元素视图中吗? - Andy G
还有,请直接在此处发布相关代码,而不仅仅作为外部Codepen。 - Andy G
@AndyG 是的,没错。 - Antony Moss
@Amessihel 嘿,我已经添加了 CSS。 - Antony Moss
2个回答

12
你可以遵循网站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);

根据 MDN 页面 displayed by the MDN page 显示的浏览器兼容性,getBoundingClientRect() 在 Chrome 和 Firefox(>= 3.5)中得到完全支持。此处提供的解决方案在大多数使用的浏览器中得到完全支持(对于某些移动版本尚不清楚)。
根据 Can I use... 网站提供的信息,移动浏览器(Chrome、Firefox 等)完全支持该方法,至少从特定版本开始。
最后,您可以记住一种仍处于实验阶段的解决方案,旨在替换使用方法getBoundingClientRect(),即Intersection Observer API(请参见 Tom M 的答案)。The MDN related page 解释了原因:

过去在实现交集检测时涉及使用事件处理程序和循环调用类似于 Element.getBoundingClientRect() 的方法来为受影响的每个元素构建所需信息。由于所有这些代码都在主线程上运行,即使其中之一也会导致性能问题。当网站加载这些测试时,情况可能变得非常丑陋。


1
请注意 isInViewportisInViewPort 的拼写错误。 - Toni Michel Caubet

6

做得很好,但我需要支持IE-11。 - Antony Moss
@AntonyMoss添加了一个链接到IntersectionObserver Polyfill,以防您仍然想尝试它。 - Tom M
由于某些原因,即使元素在视口之外(初始加载),它仍然在运行。 - Abzoozy
@Abzoozy 我建议你看一下 https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API 上的示例,并从那里开始构建符合你需求的东西。 - Tom M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接