FLIP动画思路做一个交换dom元素过渡动画

概述

最近在看vueDraggle的时候,加上列拖拽的动画就很炫酷,这里自己根据flip动画思路,做一个简单的玩玩。

最终效果

下面这个动画看似简单,其实还有不太好想,各位可以不看代码自己动手试试。

什么是flip

LIP

FLIP 分别是 First 、Last 、Invert 、 Play 四个单词的缩写;

First

元素的起始状态,例如位置、大小、形状、颜色等信息

Last

元素运动后的终止状态

Invert

元素的变化过程,也就是最终状态相对于其实状态,有哪些属性发生了改变。例如:位置向右移动了100px,颜色从黄色变为了橙色。将元素所有发生了变化的属性全部统计出来。

Play

执行动画过程,将所有发生变化的属性,从其实状态过渡到结束状态。可以设置过渡的时间、过渡方式等。可以通过上述的几种方式来实现这个过程。

实现

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>
    <style>
      * {
        margin: 0;
        padding: 0;
        list-style: none;
      }
      .container {
        width: 70%;
        border: 1px solid #ccc;
        box-sizing: border-box;
        margin: 30px auto;
        padding: 20px;
      }
      li {
        border: 1px solid #000;
        height: 60px;
        line-height: 60px;
        text-align: center;
        transition: all 0.3s;
        margin-bottom: 5px;
        background-color: green;
      }
      .list-wrap {
        width: 300px;
        margin: auto;
      }
    </style>
  </head>
  <body>
    <button class="operate-btn">操作</button>
    <div class="container">
      <ul class="list-wrap">
        <li class="list-item">item1</li>
        <li class="list-item">item2</li>
        <li class="list-item">item3</li>
        <li class="list-item">item4</li>
        <li class="list-item">item5</li>
        <li class="list-item">item6</li>
      </ul>
    </div>

    <script>
      const list = document.querySelectorAll(".list-item");
      const ul = document.querySelector("ul");
      const operateBtn = document.querySelector(".operate-btn");

      function randomIndex() {
        return Math.floor(Math.random() * list.length);
      }

      function swapNodes(node1, node2) {
        const parent = node1.parentNode;
        const node1NextElementSibling = node1.nextElementSibling;
        const node2NextElementSibling = node2.nextElementSibling;
        if (node2NextElementSibling) {
          parent.insertBefore(node1, node2NextElementSibling);
        } else {
          parent.appendChild(node1);
        }
        if (node1NextElementSibling) {
          parent.insertBefore(node2, node1NextElementSibling);
        } else {
          parent.appendChild(node2);
        }
      }
      function handleMove() {
        const animateQueen = [];
        let isPending = false;
        function animate(originIndex, targetIndex) {
          const originListItem = list[originIndex];
          const targetListItem = list[targetIndex];
          const moveDistance =
            targetListItem.getBoundingClientRect().top -
            originListItem.getBoundingClientRect().top;
          const originListItemAnimation = originListItem.animate(
            [
              {
                transform: " translateY(0)px",
              },
              {
                transform: `translateY(${moveDistance}px)`,
              },
            ],
            {
              duration: 700,
              easing: "linear",
            }
          );
          const targetListItemAnimation = targetListItem.animate(
            [
              {
                transform: "translateY(0)px",
              },
              {
                transform: `translateY(${-moveDistance}px)`,
              },
            ],
            {
              duration: 700,
              easing: "linear",
            }
          );
          // 动画先不要执行
          originListItemAnimation.pause();
          targetListItemAnimation.pause();
          // 将对应动画追加到任务队列中
          animateQueen.push({
            originListItemAnimation,
            targetListItemAnimation,
            executeCount: 0, //动画正在执行的个数
            isSwap: false, //是否完成交换
          });
          // 执行动画
          function executeSwapElements(animationItem) {
            isPending = false;
            animationItem.executeCount++;
            if (animationItem.executeCount === 2 && !animationItem.isSwap) {
              swapNodes(originListItem, targetListItem);
              animationItem.isSwap = true;
            }
          }
          // 递归检测上一个动画是否完成
          function _run() {
            while (animateQueen.length && !isPending) {
              let current = animateQueen.shift();
              current.originListItemAnimation.play();
              current.targetListItemAnimation.play();
              isPending = true;
              current.originListItemAnimation.onfinish = () => {
                isPending = false;
                executeSwapElements(current);
                _run();
              };
              current.originListItemAnimation.onfinish = () => {
                isPending = false;
                executeSwapElements(current);
                _run();
              };
            }
          }
          _run();
        }
        // 每个元素都执和其他随机元素执行一次交换动画
        for (let i = 0; i < list.length; i++) {
          animate(i, randomIndex());
        }
      }
      operateBtn.addEventListener("click", () => {
        handleMove();
      });
    </script>
  </body>
</html>

总结

  • 利用Element: animate()不会对dom元素的结构和属性产生副作用
  • 每次执行下一个动画先保证上一个执行完毕,这里用到了链式操作
  • 动画执行完毕,交换两个dom元素的位置
相关推荐
bugcome_com44 分钟前
JWT 知识小课堂:从入门到精通,一文吃透 JSON Web Token
前端·json
To_OC1 小时前
写了三天 Next.js,我的 React 世界观被拆了又装回去
前端·全栈·next.js
独泪了无痕1 小时前
viewer.js 安装与配置指南:实现图片预览功能
前端·vue.js
起床学FPGA3 小时前
AI回答问题之后,会自动跳到最下面的问题
开发语言·javascript·ecmascript
Fluxart.ai4 小时前
电商商品图审核怎么自动化?规则引擎、人工复核与发布门禁
java·前端·自动化
why技术4 小时前
AI 写的文章,可能都带着手敲一遍都去不掉的“隐形水印”。
前端·人工智能·后端
愚公搬代码4 小时前
【愚公系列】《Android应用案例开发大全》016-LBS类应用掌上杭州(辅助工具类的开发)
android·前端
CodeSheep4 小时前
又一个华为天才少年,离职了!
前端·后端·程序员
kyriewen4 小时前
面试官说"打开你的AI工具"——我才发现,他考的根本不是写代码
前端·人工智能·面试
IT_陈寒5 小时前
Vue的双向绑定把我坑惨了,原来这个场景不能用
前端·人工智能·后端