技术栈:Vue3,核心API:nextTick、dragstart事件、dragover事件
<script setup lang="ts">
import { ref, reactive, nextTick, onMounted } from 'vue';
import { createChildElementRectMap } from '../../utils/flip';
// import { throttle } from '../../utils/debounce';
const state = reactive({
list: [1, 2],
});
const ul = ref<HTMLElement | null>(null);
const first = ref<HTMLElement | null>(null);
const next = ref<HTMLElement | null>(null);
onMounted(() => {
for (let i = 1; i <= 100; i++) {
state.list.push(state.list.length + 1);
}
});
// 加法
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, delay: number = 400): 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: delay,
easing: 'cubic-bezier(0.25, 0.8, 0.25, 1)',
});
}
});
});
}
function dragstart(event: DragEvent): void {
const el = event.target as HTMLElement;
first.value = el;
}
// const dragover = throttle((event: MouseEvent) => {
// event.preventDefault();
// const el = event.target as HTMLElement;
// if (el.nodeName == 'UL') return;
// // const firstRect = first.value?.getBoundingClientRect();
// const firstText = first.value?.innerText;
// if (firstText == el.innerText) return;
// exchange(Number(firstText), Number(el.innerText), el);
// console.log(firstText, el.innerText);
// }, 100);
function dragover(event: DragEvent): void {
event.preventDefault();
const el = event.target as HTMLElement;
if (el.nodeName == 'UL') return;
const firstText = first.value?.innerText;
const nextText = next.value?.innerText;
if (firstText == el.innerText || nextText == el.innerText) return;
console.log(first.value, el);
exchange(Number(firstText), Number(el.innerText), el);
}
function exchange(first1: number, last: number, el: HTMLElement) {
for (let i = 0; i < state.list.length; i++) {
if (state.list[i] == first1) {
state.list[i] = last;
} else if (state.list[i] == last) {
state.list[i] = first1;
}
}
next.value = el;
const res = createChildElementRectMap(ul.value);
invert(res);
}
</script>
<template>
<div class="test">
<el-scrollbar>
<el-button type="primary" plain @click="increment(50)">加</el-button>
<el-button type="primary" plain @click="decrease(25)">减</el-button>
<el-button type="primary" plain @click="increment(25, true)"
>乱加</el-button
>
<el-button type="primary" plain @click="decrease(15, true)"
>乱减</el-button
>
<el-button plain type="primary" @click="sort">打乱</el-button>
<ul ref="ul" @dragstart="dragstart" @dragover="dragover">
<li draggable="true" v-for="item in state.list" :key="item" ref="li">
{{ item }}
</li>
</ul>
</el-scrollbar>
</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;
color: white;
}
}
}
</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()]))
}