技术栈为
Vue3,核心API为nextTick
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue';
import { createChildElementRectMap } from '../../utils/flip';
const state = reactive({
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
});
const ul = ref<HTMLElement | null>(null);
// 加法
function increment(total: number, sort: boolean = false): void {
const arr = state.list;
for (let i = 1; i <= total; i++) {
arr.unshift(state.list.length + 1);
if (sort) {
arr.sort(() => 0.5 - Math.random());
}
}
state.list = arr;
const res = createChildElementRectMap(ul.value);
invert(res);
}
// 减法
function decrease(total: number, sort: boolean = false): void {
const arr = state.list;
for (let i = 1; i <= total; i++) {
arr.shift();
if (sort) {
arr.sort(() => 0.5 - Math.random());
}
}
state.list = arr;
const res = createChildElementRectMap(ul.value);
invert(res);
}
// 乱序
function sort(): void {
state.list = state.list.sort(() => 0.5 - Math.random());
const res = createChildElementRectMap(ul.value);
invert(res);
}
// 平移执行动画
function invert(res: any): void {
nextTick(() => {
const res2 = createChildElementRectMap(ul.value);
res2.forEach((item, node) => {
const current = res.get(node);
if (current) {
const invert = {
left: current.left - item.left,
top: current.top - item.top,
};
const keyframes = [
{
transform: `translate(${invert.left}px, ${invert.top}px)`,
},
{ transform: 'translate(0, 0)' },
];
node?.animate(keyframes, {
duration: 1000,
easing: 'cubic-bezier(0.25, 0.8, 0.25, 1)',
});
}
});
});
}
</script>
<template>
<div class="test">
<button @click="increment(50)">加</button
><button @click="decrease(25)">减</button>
<button @click="increment(25, true)">乱加</button
><button @click="decrease(15, true)">乱减</button
><button @click="sort">打乱</button>
<ul ref="ul">
<li v-for="item in state.list" :key="item" ref="li">{{ item }}</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.test {
height: 100%;
box-sizing: border-box;
padding: 50px;
background-color: rgb(189, 201, 237);
ul {
display: flex;
flex-wrap: wrap;
list-style: none;
li {
width: 50px;
height: 50px;
text-align: center;
line-height: 50px;
background-color: chocolate;
border-radius: 4px;
margin-right: 10px;
margin-bottom: 10px;
box-shadow: 5px 10px 10px gray;
}
}
}
</style>
//flip.ts
export function createChildElementRectMap(node: HTMLElement | null): Map<HTMLElement | null | undefined, any> {
if (!node) return new Map()
const elements = Array.prototype.slice.call(node.children)
return new Map(elements.map(item => [item, item.getBoundingClientRect()]))
}