用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>
相关推荐
hhcccchh1 天前
学习vue第八天 Vue3 模板语法和内置指令 - 简单入门
前端·vue.js·学习
yyf198905251 天前
Vue 框架相关中文文献
前端·javascript·vue.js
粥里有勺糖1 天前
开发一个美观的 VitePress 图片预览插件
前端·vue.js·vitepress
行者961 天前
Flutter适配OpenHarmony:国际化i18n实现中的常见陷阱与解决方案
开发语言·javascript·flutter·harmonyos·鸿蒙
陟上青云1 天前
一篇文章带你搞懂原型和原型链
前端
我的写法有点潮1 天前
推荐几个国外比较流行的UI库(上)
前端·javascript·css
鹏多多1 天前
jsx/tsx使用cssModule和typescript-plugin-css-modules
前端·vue.js·react.js
ssshooter1 天前
复古话题:Vue2 的空格间距切换到 Vite 后消失了
前端·vue.js·面试
IamZJT_1 天前
拒绝做 AI 的“饲养员” ❌:前端程序员在 AI 时代的生存与进化指南 🚀
前端·ai编程
MM_MS1 天前
Halcon控制语句
java·大数据·前端·数据库·人工智能·算法·视觉检测