概述
最近在看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元素的位置