用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>
相关推荐
拉不动的猪5 分钟前
Axios 请求取消机制详解
前端·javascript·面试
该用户已不存在8 分钟前
2025 年 8 款最佳远程协作工具
前端·后端·远程工作
lxh011315 分钟前
螺旋数组题解
前端·算法·js
E***U94518 分钟前
前端安全编程实践
前端·安全
老华带你飞28 分钟前
海产品销售系统|海鲜商城购物|基于SprinBoot+vue的海鲜商城系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·海鲜商城购物系统
x***B41138 分钟前
React安全编程实践
前端·安全·react.js
能鈺CMS39 分钟前
内容付费系统全面解析:构建知识变现体系的最强工具(2025 SEO 深度专题)
大数据·人工智能·html
D***t1311 小时前
前端微服务案例
前端
哀木1 小时前
诶,这么好用的 mock 你怎么不早说
前端
Lear1 小时前
UniApp PDF文件下载与预览功能完整实现指南
前端