效果
原理详解
1.beforeUpdate 获取first 变化前位置 (以id建立map映射)
2.updated 获取变化后位置 last
3.禁用transition并transform元素回初始位置
4.异步恢复transition 并取消 transform
代码
<template>
<div ref="container">
<div style="display: flex">
<div style="width:400px">
<!--仅展示三级数据,若需递归自行修改-->
<div class="item" :id="i.value" v-for="i in locArr" :key="i.value">
<div>{{i.label}}</div>
<div class="item" :id="j.value" v-for="j in i.children" :key="j.value">
<div>{{j.label}}</div>
<div class="item" :id="k.value" v-for="k in j.children" :key="k.value">
<div>{{k.label}}</div>
</div>
</div>
</div>
</div>
<div style="padding: 10px">
<button @click="shuffle">重新随机数据</button>
<button @click="shuffleOne">打乱一级顺序</button>
<button @click="shuffleTwo">打乱二级顺序</button>
</div>
</div>
</div>
</template>
<script>
/*
1.beforeUpdate 获取first 变化前位置 (以id建立map映射),
2.updated 获取变化后位置 last ,
3.禁用transition并transform元素回初始位置,
4.异步恢复transition 并取消 transform
*/
/**
* 乱序数组
*/
Array.prototype.shuffle = function () {
let input = this;
for (let i = input.length - 1; i >= 1; i--) {
let ri = ~~(Math.random() * (i + 1));
input[i] = [input[ri], input[ri] = input[i]][0];
}
return input;
};
/**
* 随机多级数据
* @param root
* @param len
* @param level
*/
const treeOptions = ({ root = '0', len = 40, level = 4 }) => {
let levelPoolLen = level, originPoolLen = len - levelPoolLen
//创建 level 个 LevelPool ,并为每个 LevelPool 初始化一个元素
let levelPool = []
for (let i = 1; i <= levelPoolLen; i++) {
levelPool.push([{ id: '', parentId: root, label: '', value: '' }])
}
//初始化指定数量的元素并随机丢入 LevelPool 中
for (let i = 0; i < originPoolLen; i++) {
let currLevel = ~~(Math.random() * level)
levelPool[currLevel].push({ id: '', parentId: root, label: '', value: '' })
}
let nextId = 0
//由前到后依次遍历LevelPool,遍历本级池中各项并随机从前一级池中选取一项作为当前项的父级
for (let i = 0, item; (item = levelPool[0][i]) != null; i++) {
item.id = ++nextId
item.parentId = root
item.label = `label${item.id}`
item.value = `value${item.id}`
item.level = 0
}
for (let i = 1; i < levelPoolLen; i++) {
let prevLevelPool = levelPool[i - 1]
let prevLevelPoolLen = prevLevelPool.length
for (let j = 0, item; (item = levelPool[i][j]) != null; j++) {
//随机父节点
let parent = prevLevelPool[~~(Math.random() * prevLevelPoolLen)]
if (!parent.children) {
parent.children = []
}
item.id = `${parent.id}-${parent.children.length}`
item.parentId = parent.id
item.label = `label${item.id}`
item.value = `value${item.id}`
item.level = i
parent.children.push(item)
}
}
//将一级levelPool合并输出
return levelPool[0]
}
let firstMap = [], last = []
export default {
name: "FLIP",
data() {
return {
locArr: treeOptions({ level: 3, len: 40 })
}
},
beforeUpdate() {
this.$refs.container.querySelectorAll('.item').forEach(d => firstMap[d.id] = d.getBoundingClientRect())
},
updated() {
let lastRect, firstRect, transX, transY, $lastNodes = this.$refs.container.querySelectorAll('.item')
$lastNodes.forEach(d => {
lastRect = d.getBoundingClientRect()
firstRect = firstMap[d.id]
if (firstRect) {
transX = firstRect.left - lastRect.left
transY = firstRect.top - lastRect.top
d.style.transition = 'none'
d.style.transform = `translate3D(${transX}px,${transY}px,0) `
}
})
setTimeout(_ => {
$lastNodes.forEach(d => {
d.style.transition = ''
d.style.transform = ''
})
})
},
methods: {
shuffle() {
// this.locArr[1].children.push( this.locArr.shift())
this.locArr = treeOptions({ level: 3, len: 40 })
},
shuffleOne() {
this.locArr = [...this.locArr.shuffle()]
},
shuffleTwo() {
const vm = this
this.locArr.forEach((d, index) => {
if (d.children) {
d.children = [...d.children.shuffle()]
}
})
}
}
}
</script>
<style scoped>
.item {
transition: all 1s;
margin-left: 20px;
}
.item>div:not(.item){
padding:5px;
border-radius:2px;
background: #fff;
box-shadow: #999999 2px 2px 5px 1px;
}
button{
display: block;
margin: 10px;
cursor: pointer;
}
</style>