用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>
相关推荐
brief of gali10 分钟前
记录一个奇怪的前端布局现象
前端
前端拾光者1 小时前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
Json_181790144801 小时前
电商拍立淘按图搜索API接口系列,文档说明参考
前端·数据库
风尚云网1 小时前
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
前端·css·学习·html·html5·风尚云网
木子02042 小时前
前端VUE项目启动方式
前端·javascript·vue.js
GISer_Jing2 小时前
React核心功能详解(一)
前端·react.js·前端框架
捂月2 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
深度混淆2 小时前
实用功能,觊觎(Edge)浏览器的内置截(长)图功能
前端·edge
Smartdaili China2 小时前
如何在 Microsoft Edge 中设置代理: 快速而简单的方法
前端·爬虫·安全·microsoft·edge·社交·动态住宅代理
秦老师Q2 小时前
「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用
前端·chrome·edge