FLIP动画

91 阅读1分钟

技术栈为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()]))
}