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元素的位置
相关推荐
清灵xmf17 分钟前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据23 分钟前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_3901617732 分钟前
防抖函数--应用场景及示例
前端·javascript
334554321 小时前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test1 小时前
js下载excel示例demo
前端·javascript·excel
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v1 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript