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元素的位置
相关推荐
weixin-a153003083161 小时前
【playwright篇】教程(十七)[html元素知识]
java·前端·html
ai小鬼头1 小时前
AIStarter最新版怎么卸载AI项目?一键删除操作指南(附路径设置技巧)
前端·后端·github
wen's1 小时前
React Native 0.79.4 中 [RCTView setColor:] 崩溃问题完整解决方案
javascript·react native·react.js
一只叫煤球的猫2 小时前
普通程序员,从开发到管理岗,为什么我越升职越痛苦?
前端·后端·全栈
vvilkim2 小时前
Electron 自动更新机制详解:实现无缝应用升级
前端·javascript·electron
vvilkim2 小时前
Electron 应用中的内容安全策略 (CSP) 全面指南
前端·javascript·electron
aha-凯心2 小时前
vben 之 axios 封装
前端·javascript·学习
漫谈网络2 小时前
WebSocket 在前后端的完整使用流程
javascript·python·websocket
遗憾随她而去.3 小时前
uniapp 中使用路由导航守卫,进行登录鉴权
前端·uni-app
xjt_09013 小时前
浅析Web存储系统
前端