CSS 使用 Intersection Observer 时动画滞后或跳跃 API

CSS Animations laggy or jumpy when using Intersection Observer API

在我的 HTML/CSS 网络项目中,我想使用 @keyframes 包含一个基于 CSS 的动画,效果非常好。 然后我尝试添加一种可能性,以便仅在使用 IntersectionObserver - Intersection Observer API.

对观众可见时才加载动画

For that I followed this tutorial - (部分:“当元素滚动到视图中时添加 class”)

到目前为止还不错,但是我的问题来了

我有三个 classes 从右边滑入并且应该以不同的宽度停止。 出于某种原因,尽管我一添加 intersection observer,动画就非常滞后。 他们滑进去,突然停下来,再滑一点,然后跳到最终位置,而不是像以前那样平稳地滑

这是我所做的:

const observer = new IntersectionObserver(entries => {
    // Loop over the entries
    entries.forEach(entry => {
      // If the element is visible
      if (entry.isIntersecting) {
        // Add the animation class
        entry.target.classList.add('rectangle-animation-one','rectangle-animation-two','rectangle-animation-three');

      }
    });
  });
  
  observer.observe(document.querySelector('.rectangle-one'));
  observer.observe(document.querySelector('.rectangle-two'));
  observer.observe(document.querySelector('.rectangle-three'));
.section-flex{
  display:flex;
  flex-direction: column;
  padding: 30px;
}
.rectangle-one  {
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 80%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #EC6F72;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-one {
  animation-duration: 1s;
  animation-name: slidein-one;
  animation-iteration-count: 1;
  animation-direction:normal;
}
@keyframes slidein-one {
  from {
    margin-left:100%;
    width:300%
  }

  to {
    margin-left:20%;
  }
}
.rectangle-two  {
  margin-top: 0px;
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 55%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #FFB94D;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-two {
    animation-duration: 1s;
    animation-name: slidein-two;
    animation-iteration-count: 1;
    animation-direction:normal;
  }
  @keyframes slidein-two {
    from {
      margin-left:100%;
      width:300%
    }
  
    to {
      margin-left:45%;
    }
  }
  
.rectangle-three  {
  margin-top: 0px;
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 40%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #3C51AA;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-three {
    animation-duration: 1s;
    animation-name: slidein-three;
    animation-iteration-count: 1;
    animation-direction:normal;
  }
  @keyframes slidein-three {
    from {
      margin-left:100%;
      width:300%
    }
  
    to {
      margin-left:60%;
    }
  }
<div class="sectionflex">
        <div class="rectangle-one">
        <p>TEXT 1</p>
        </div>
        <div class="rectangle-two">
        <p>TEXT 2</p>
        </div>
        <div class="rectangle-three">
        <p>TEXT 3</p>
        </div>
    </div>
    <script src="app.js"></script>

关于我在这里做错了什么有什么想法吗?我几乎可以肯定这是我尝试在 javascript 代码中添加所有三个动画的方式,我已经尝试了各种组合但无法正确完成。

感谢您的帮助

好吧,老实说,上面的代码没有按照您认为的那样做可能有很多原因,但我认为主要是因为,无论是转换还是放置边距等,您的 off-screen 内容跨越了进入屏幕之间的界线,这会破坏您的动画效果。我建议使用更简单的设置来完成这项工作。

一般来说,我们希望将您的内容包裹在另一个不会移动但用作出现在屏幕上的触发器的元素中。当环绕(在我的示例中为 section)部分相交时,它会添加一个 class,这应该会触发 children.

上的 transition

当某些东西在两种状态之间切换时,转换很有用 - 这意味着您实际上不必自己记住动画。当您需要具有不同值的多个关键帧时,最好使用动画,但这里有两个状态,每个状态都有一个值。

这看起来像这样:

const observer = new IntersectionObserver(entries => {

    // Loop over the entries
    entries.forEach(entry => {
      
      // Let's just toggle a class here
      // We can respond to it however we want in CSS
      entry.target.classList.toggle('in-view', entry.isIntersecting);

    });
    
});
  
observer.observe(document.querySelector('section#one', { treshold: 1 }));
observer.observe(document.querySelector('section#two', { treshold: 1 }));
observer.observe(document.querySelector('section#three', { treshold: 1 }));
main {
  display: flex;
  flex-direction: column;
  padding: 30px;
  align-items: flex-end;
}
/* I have made this 100vh high, just so we can see the effect properly. Remove the 100vh below to see something more akin to your original. */
section {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: flex-end;
}
/* I have made this bit shared, so we don't get lost in the repeated CSS. It's fine otherwise, but this way we don't focus on the wrong thing. */
section > div {
  height: 125px;
  line-height: 125px;
  width: 80%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #EC6F72;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
  transform: translateX(100vw);
  /* Instead of an animation, perhaps a transition will do better. */
  transition: transform .4s;
}
section.in-view > div {
  /* When a section contains in view, the div inside it will slide in using a smooth transform. */
  transform: translateX(0);
}
section#two > div  {
  width: 55%;
  color: #fff;
  background: #FFB94D;
}
section#three > div  {
  width: 40%;
  color: #fff;
  background: #3C51AA;
}
<main>
  <!--
  So we need to split this up, one wrapping <section> (the type of element doesn't matter) will be used to detect when the content should come into view.
  -->
  <section id="one">
    <div>
      <p>TEXT 1</p>
    </div>
  </section>
  <section id="two">
    <div>
      <p>TEXT 2</p>
    </div>
  </section>
  <section id="three">
    <div>
      <p>TEXT 3</p>
    </div>
  </section>
</main>

希望对您有所帮助。我知道这不是直接解决动画,但动画很难实现,所以如果你能用过渡来解决它,那通常是最合理的选择。

更新

在看到您的回应和答复后,我确实认为有必要指出一点,您不需要三个 ResizeObservers(不要那样做,它会使您的代码很快变得混乱!)您正在尝试添加特定的 classes 来触发特定的动画,因此您需要做的就是找到一种方法来使用相同的代码来完成此操作。在这种情况下,我向所有三个框添加了一个 data-name="one",因此我们可以连接一个字符串以生成 class,例如 rectangle-animation-one.

const observer = new IntersectionObserver(entries => {
  // Loop over the entries
  entries.forEach(entry => {
    // If the element is visible
    if (entry.isIntersecting) {
      // Add the animation class
      entry.target.classList.add('rectangle-animation-' + entry.target.dataset.name, entry.isIntersecting);
    }
  });
});

observer.observe(document.querySelector('.rectangle-one'));
observer.observe(document.querySelector('.rectangle-two'));
observer.observe(document.querySelector('.rectangle-three'));
/*
const observer2 = new IntersectionObserver(entries => {
  // Loop over the entries
  entries.forEach(entry => {
    // If the element is visible
    if (entry.isIntersecting) {
      // Add the animation class
      entry.target.classList.add('rectangle-animation-two', entry.isIntersecting);
    }
  });
});

observer2.observe(document.querySelector('.rectangle-two'));

const observer3 = new IntersectionObserver(entries => {
  // Loop over the entries
  entries.forEach(entry => {
    // If the element is visible
    if (entry.isIntersecting) {
      // Add the animation class
      entry.target.classList.add('rectangle-animation-three', entry.isIntersecting);
    }
  });
});
observer3.observe(document.querySelector('.rectangle-three'));
*/
.section-flex{
  display:flex;
  flex-direction: column;
  padding: 30px;
}
.rectangle-one  {
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 80%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #EC6F72;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-one {
  animation-duration: 1s;
  animation-name: slidein-one;
  animation-iteration-count: 1;
  animation-direction:normal;
}
@keyframes slidein-one {
  from {
    margin-left:100%;
    width:300%
  }

  to {
    margin-left:20%;
  }
}
.rectangle-two  {
  margin-top: 0px;
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 55%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #FFB94D;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-two {
    animation-duration: 1s;
    animation-name: slidein-two;
    animation-iteration-count: 1;
    animation-direction:normal;
  }
  @keyframes slidein-two {
    from {
      margin-left:100%;
      width:300%
    }
  
    to {
      margin-left:45%;
    }
  }
  
.rectangle-three  {
  margin-top: 0px;
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 40%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #3C51AA;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-three {
    animation-duration: 1s;
    animation-name: slidein-three;
    animation-iteration-count: 1;
    animation-direction:normal;
  }
  @keyframes slidein-three {
    from {
      margin-left:100%;
      width:300%
    }
  
    to {
      margin-left:60%;
    }
  }
<div class="sectionflex">
        <div class="rectangle-one" data-name="one">
        <p>TEXT 1</p>
        </div>
        <div class="rectangle-two" data-name="two">
        <p>TEXT 2</p>
        </div>
        <div class="rectangle-three" data-name="three">
        <p>TEXT 3</p>
        </div>
    </div>
    <script src="app.js"></script>

更好的做法是切换动画 class,然后在 CSS:

中这样定义它们
.rectangle-one.animation { ... }

那么您就有了真正通用的动画触发方法。

希望对您有所帮助!

在试用它并从@somethinghere 获得一些帮助后(非常感谢!)我弄清楚了我这边出了什么问题..

对于遇到同样问题的其他人,我不能简单地通过我的 .js 文件中的一个持续观察者添加所有动画。 我现在为每个 class 添加了一个单独的观察者常量,它现在看起来像这样:

编辑:请查看@somethinghere 的答案中的更新以获得更清晰的版本

const observer = new IntersectionObserver(entries => {
  // Loop over the entries
  entries.forEach(entry => {
    // If the element is visible
    if (entry.isIntersecting) {
      // Add the animation class
      entry.target.classList.add('rectangle-animation-one', entry.isIntersecting);
    }
  });
});

observer.observe(document.querySelector('.rectangle-one'));

const observer2 = new IntersectionObserver(entries => {
  // Loop over the entries
  entries.forEach(entry => {
    // If the element is visible
    if (entry.isIntersecting) {
      // Add the animation class
      entry.target.classList.add('rectangle-animation-two', entry.isIntersecting);
    }
  });
});

observer2.observe(document.querySelector('.rectangle-two'));

const observer3 = new IntersectionObserver(entries => {
  // Loop over the entries
  entries.forEach(entry => {
    // If the element is visible
    if (entry.isIntersecting) {
      // Add the animation class
      entry.target.classList.add('rectangle-animation-three', entry.isIntersecting);
    }
  });
});
observer3.observe(document.querySelector('.rectangle-three'));
.section-flex{
  display:flex;
  flex-direction: column;
  padding: 30px;
}
.rectangle-one  {
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 80%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #EC6F72;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-one {
  animation-duration: 1s;
  animation-name: slidein-one;
  animation-iteration-count: 1;
  animation-direction:normal;
}
@keyframes slidein-one {
  from {
    margin-left:100%;
    width:300%
  }

  to {
    margin-left:20%;
  }
}
.rectangle-two  {
  margin-top: 0px;
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 55%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #FFB94D;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-two {
    animation-duration: 1s;
    animation-name: slidein-two;
    animation-iteration-count: 1;
    animation-direction:normal;
  }
  @keyframes slidein-two {
    from {
      margin-left:100%;
      width:300%
    }
  
    to {
      margin-left:45%;
    }
  }
  
.rectangle-three  {
  margin-top: 0px;
  margin-left: auto;
  height: 125px;
  line-height: 125px;
  width: 40%;
  font-size: 20px;
  color: #fff;
  text-align: center;
  background: #3C51AA;
  box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-three {
    animation-duration: 1s;
    animation-name: slidein-three;
    animation-iteration-count: 1;
    animation-direction:normal;
  }
  @keyframes slidein-three {
    from {
      margin-left:100%;
      width:300%
    }
  
    to {
      margin-left:60%;
    }
  }
<div class="sectionflex">
        <div class="rectangle-one">
        <p>TEXT 1</p>
        </div>
        <div class="rectangle-two">
        <p>TEXT 2</p>
        </div>
        <div class="rectangle-three">
        <p>TEXT 3</p>
        </div>
    </div>
    <script src="app.js"></script>