用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>
相关推荐
ekskef_sef9 分钟前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine64133 分钟前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云1 小时前
npm淘宝镜像
前端·npm·node.js
dz88i81 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr1 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
程序员_三木1 小时前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
顾平安2 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网2 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工2 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染