Flip效果、原生js实现过渡动画

使用原生JS实现Flip过渡效果

阅读量:1051

点赞量:33

评论量:0
Skill
标签摘要
使用原生JS实现Flip过渡效果
正文


效果:

GIF.gif

实现思路:

在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效果,可以在此基础上继续扩展,比如宽高的过渡。

发布于: 12/30/2022, 10:57:16 AM
最后更新: 12/3/2024, 3:30:30 AM