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元素的位置
相关推荐
Justin3go1 天前
HUNT0 上线了——尽早发布,尽早发现
前端·后端·程序员
怕浪猫1 天前
第一章 JSX 增强特性与函数组件入门
前端·javascript·react.js
铅笔侠_小龙虾1 天前
Emmet 常用用法指南
前端·vue
幻影星空VR元宇宙1 天前
飞行影院设备价格解析及性价比分析
css·百慕大冒险·幻影星空
钦拆大仁1 天前
跨站脚本攻击XSS
前端·xss
前端小L1 天前
贪心算法专题(十):维度权衡的艺术——「根据身高重建队列」
javascript·算法·贪心算法
VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue校园社团管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
Fortunate Chen1 天前
类与对象(下)
java·javascript·jvm
ChangYan.1 天前
直接下载源码但是执行npm run compile后报错
前端·npm·node.js