用IntersectionObserver API实现类似小米网站的页面滚动动画

概述

最近发现很多网站都有滚动条滚动到可视区然后执行特定的动画,这样给访问的用户可以代码更好的用户体验,比如小米汽车的官网,当我们滚动到可视区的时候,会有文字或者图片从下方然后慢慢升起的过渡效果,很是炫酷,这里我们可以借助IntersectionObserverElement.animate很方便实现类似的效果,我相信理解下面这个动画实现后,再去看其他的可视区动画效果的原理都能理解,万变不离其中。

最终效果

两个重要的工具

IntersectionObserver

IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root),简单来说就是可以观察元素到文档可视区的变化(目前我们这里需要的)

Element.animate()

Element 接口的 animate() 方法是创建一个新的 Animation 的便捷方法,将它应用于元素,然后运行动画。它将返回一个新建的 Animation 对象实例,简单来说就是可以api调用方式追加元素动画,而不需要使用三方库,这是非常强大的,可已暂停,开始动画,大大方便了我们自定义动画的灵活变通。

实现

js 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    * {
      padding: 0;
      margin: 0;
      list-style: none;
    }
    html,
    body {
      height: 100%;
    }
    .item {
      width: 500px;
      height: 200px;
      background-color: pink;
      margin: 10px;
      text-align: center;
      line-height: 200px;
    }
    .container-wrap {
      display: flex;
      justify-content: center;
    }
  </style>
  <body>
    <div class="container-wrap">
      <ul class="container"></ul>
    </div>
    <script>
      const domMap = new WeakMap();
      // 随机生成颜色
      function generateRandomColor() {
        let r = Math.floor(Math.random() * 255);
        let g = Math.floor(Math.random() * 255);
        let b = Math.floor(Math.random() * 255);
        return `rgb(${r},${g},${b})`;
      }
      // 判断元素是否在可视区已经上面
      function checkElementShowTopViewPort(dom) {
        return (
          dom.getBoundingClientRect().top <
          document.documentElement.clientHeight
        );
      }
      //  添加动画
      function addAnimate(dom) {
        const animate = dom.animate(
          [
            {
              transform: "translateY(100px) scale(0)",
              opacity: 0,
            },
            {
              transform: "translateY(0px) scale(1)",
              opacity: 1,
            },
          ],
          {
            duration: 300,
          }
        );
        animate.pause();
        domMap.set(dom, { animate });
      }
      // 创建dom元素
      function createDom() {
        const parentWrap = document.querySelector(".container");
        for (let i = 0; i < 100; i++) {
          const li = document.createElement("li");
          li.className = "item " + "item" + i;
          li.innerHTML = "item" + i;
          li.style.background = generateRandomColor();

          parentWrap.appendChild(li);
        }
      }
      // 添加元素位置观察
      function addDomObserver(li) {
        const intersectionObserver = new IntersectionObserver((entries) => {
          entries.forEach((entry) => {
            if (entry.isIntersecting) {
              const targetDomDataMap = domMap.get(entry.target);
              if (!targetDomDataMap) return;
              if (!targetDomDataMap.loaded) {
                targetDomDataMap.animate.play();
                // 标识加载过了
                targetDomDataMap.loaded = true;
                // 动画执行完毕,关闭动画和取消监听
                targetDomDataMap.animate.onfinish = () => {
                  targetDomDataMap.animate.cancel();
                  intersectionObserver.unobserve(entry.target);
                };
              }
            }
          });
        });
        // 开始监听
        const allItemList = document.querySelectorAll(".item");
        for (let i = 0; i < allItemList.length; i++) {
          intersectionObserver.observe(allItemList[i]);
        }
      }
      // 创建动画(dom创建完成之后)
      function createAnimate() {
        const allItemList = document.querySelectorAll(".item");
        for (let i = 0; i < allItemList.length; i++) {
          console.log(checkElementShowTopViewPort(allItemList[i]));
          if (!checkElementShowTopViewPort(allItemList[i])) {
            addAnimate(allItemList[i]);
          }
        }
      }
      createDom();
      createAnimate();
      addDomObserver();
    </script>
  </body>
</html>
相关推荐
雪碧聊技术27 分钟前
深入解析Vue中v-model的双向绑定实现原理
前端·javascript·vue.js·v-model
快起来别睡了29 分钟前
手写 Ajax 与 Promise:从底层原理到实际应用
前端
打不着的大喇叭1 小时前
uniapp的光标跟随和打字机效果
前端·javascript·uni-app
无我Code1 小时前
2025----前端个人年中总结
前端·年终总结·创业
程序猿阿伟1 小时前
《前端路由重构:解锁多语言交互的底层逻辑》
前端·重构
Sun_light2 小时前
6个你必须掌握的「React Hooks」实用技巧✨
前端·javascript·react.js
爱学习的茄子2 小时前
深度解析JavaScript中的call方法实现:从原理到手写实现的完整指南
前端·javascript·面试
莫空00002 小时前
Vue组件通信方式详解
前端·面试
呆呆的心2 小时前
揭秘 CSS 伪元素:不用加标签也能玩转出花的界面技巧 ✨
前端·css·html
百锦再2 小时前
重新学习Vue中的按键监听和鼠标监听
javascript·vue.js·vue·计算机外设·click·up·down