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元素的位置
相关推荐
无咎.lsy1 分钟前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec9 分钟前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec11 分钟前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆1 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
JUNAI_Strive_ving1 小时前
番茄小说逆向爬取
javascript·python
看到请催我学习2 小时前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins35202 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky2 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~2 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒2 小时前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od