效果:
实现思路:
在DOM发生改变,而页面没有绘制时,可以将元素位置放到变化前的状态,然后在页面绘制时,再添加过渡动画到结束位置,从而实现效果。
实现步骤
1、记录子元素初始所在位置。
const startRect = children.map(item => item.getBoundingClientRect())
2、记录子元素结束位置。
const endRect = children.map(item => item.getBoundingClientRect())
3、将子元素位置放到初始位置。
children.forEach(item => item.style.transform = `translate3d(${startRect.left - endRect.left}px, ${startRect.top - endRect.top}px, 0px`)
4、在浏览器下次渲染时,执行过渡回到结束位置。
requestAnimationFrame(() => {
children.forEach((item) => {
item.style.transition = `transform 300ms linear`
item.style.transform = ''
})
})
// 将结束位置赋值给开始位置,用于下次调用
startRect = endRect;
全部代码
index.html
<div id="app">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
</div>
<button>打乱</button>
<script type="module" src="./flip.ts"></script>
<script>
const btn = document.querySelector('button')
const listBox = document.querySelector('#app')
const children = listBox.children
// 创建flip
const f = new MpFlip(children)
btn.addEventListener('click', function () {
const len = children.length
let index = 0
while (index < len - 1) {
const node = children[index]
const insert = children[len - 1]
listBox.insertBefore(insert, node)
index++
}
// 执行过渡动画
f.play(500)
})
</script>
flip.ts
type ElementsType = Element[] | HTMLCollection
export class MpFlip {
private ele: ElementsType | undefined
private startRect: { [key in string]: DOMRect } = {}
private endRect: { [key in string]: DOMRect } = {}
private elements: { [key in string]: HTMLElement } = {}
constructor(elements: ElementsType) {
this.ele = elements
this.saveStartRect()
}
/**
* 保存开始位置
* @returns
*/
private saveStartRect() {
if (!this.ele || !this.ele.length) {
return
}
for (let i = 0; i < this.ele.length; i++) {
const item = this.ele[i] as HTMLElement
const id = `mp-flip-${i}`
item.dataset.mpFlipId = id
this.startRect[id] = item.getBoundingClientRect()
}
}
/**
* 保存结束位置
* @returns
*/
private saveEndRect() {
if (!this.ele || !this.ele.length) {
return
}
for (let i = 0; i < this.ele.length; i++) {
const item = this.ele[i] as HTMLElement
const id = item.dataset.mpFlipId as string
item.style.transition = ''
this.elements[id] = item
this.endRect[id] = item.getBoundingClientRect()
}
}
/**
* 设置为开始Rect
*/
setStartRect() {
Object.keys(this.endRect).forEach((id) => {
const startRect = this.startRect[id]
const endRect = this.endRect[id]
const element = this.elements[id]
if (
startRect &&
(startRect.left !== endRect.left || startRect.top !== endRect.top)
) {
element.style.transform = `translate3d(${
startRect.left - endRect.left
}px, ${startRect.top - endRect.top}px, 0px`
}
})
}
/**
* 运行过度动画
* @param time 过渡时间
*/
animate(time: number = 300) {
Object.keys(this.endRect).forEach((id) => {
const element = this.elements[id]
element.style.transition = `transform ${time}ms linear`
element.style.transform = ''
})
this.startRect = this.endRect
this.endRect = {}
}
/**
* 运行过渡动画
* @param time 过渡时间
*/
public play(time?: number) {
this.saveEndRect()
this.setStartRect()
requestAnimationFrame(() => this.animate(time))
}
}
结语
一个简单的Flip效果,可以在此基础上继续扩展,比如宽高的过渡。