VUE之平移反转动画效果

Vue平移反转动画实现

实现一个如上的效果

li 元素的 Y 轴位移由 Vue 自动算出来(位置变化时平移)。

<template>
  <div id="app">
    <h2>实时排行榜</h2>

    <!-- 列表展示 -->
    <transition-group name="flip-list" tag="ul" class="list">
      <li v-for="item in sortedItems" :key="item.id" class="list-item">
        <!-- 外层 li 负责位置移动 -->
        <!-- 内层 wrapper 负责旋转 -->
        <div
          class="item-wrapper"
          :class="{
            rotatedUp: item.animate && item.trend === 'up',
            rotatedDown: item.animate && item.trend === 'down',
          }"
          @animationend="item.animate = false"
        >
          <span>{{ item.name }}</span>
          <span>{{ item.score }}</span>
          <span>{{ item.date }}</span>
          <span v-if="item.trend === 'up'" class="up">↑</span>
          <span v-else-if="item.trend === 'down'" class="down">↓</span>
          <span v-else>不变</span>
        </div>
      </li>
    </transition-group>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      items: [],
      oldItems: [],
      sortKey: 'score',
      timer: null,
    };
  },
  computed: {
    sortedItems() {
      return [...this.items].sort((a, b) => {
        if (this.sortKey === 'score') return b.score - a.score;
      });
    },
  },
  methods: {
    mockApi() {
      const baseNames = ['项目A', '项目B', '项目C', '项目D', '项目E', '项目F', '项目G', '项目H', '项目I', '项目J'];
      return baseNames.map((name, index) => ({
        id: index + 1,
        name,
        score: Math.floor(Math.random() * 100),
        date: new Date(Date.now() - Math.random() * 100000000).toISOString().slice(0, 10),
      }));
    },
    fetchData() {
      const newData = this.mockApi();

      // 新数据 + 趋势标记
      let tempItems = newData.map(newItem => {
        const oldItem = this.oldItems.find(i => i.id === newItem.id);
        if (oldItem) {
          if (newItem.score > oldItem.score) newItem.trend = 'up';
          else if (newItem.score < oldItem.score) newItem.trend = 'down';
          else newItem.trend = 'same';
        } else {
          newItem.trend = 'same';
        }
        newItem.animate = false; // 默认不旋转
        return newItem;
      });

      // 排序后的新顺序
      const sortedNew = [...tempItems].sort((a, b) => {
        if (this.sortKey === 'score') return b.score - a.score;
      });

      // 对比旧顺序,判断位置是否变化
      if (this.oldItems.length) {
        sortedNew.forEach((item, idx) => {
          const oldIdx = this.oldItems.findIndex(i => i.id === item.id);
          if (oldIdx !== -1 && oldIdx !== idx) {
            item.animate = true; // 位置变化才旋转
          }
        });
      }

      this.items = sortedNew;
      this.oldItems = JSON.parse(JSON.stringify(sortedNew));
    },
  },
  mounted() {
    this.fetchData();
    this.timer = setInterval(this.fetchData, 3000);
  },
  beforeDestroy() {
    clearInterval(this.timer);
  },
};
</script>

<style>
.list {
  list-style: none;
  padding: 0;
}
.list-item {
  display: flex;
  justify-content: space-between;
  padding: 8px;
  background: #f2f2f2;
  margin-bottom: 4px;
  border-radius: 4px;
}
.up {
  color: green;
  font-weight: bold;
}
.down {
  color: red;
  font-weight: bold;
}

/* Vue transition-group 自动处理位移动画 */
.flip-list-move {
  transition: transform 0.8s;
}

/* 内层 wrapper 控制旋转 */
.item-wrapper {
  display: flex;
  justify-content: space-between;
  width: 100%;
}

/* 上升时绿色翻转 */
.rotatedUp {
  animation: rotateXUp 0.8s linear;
}
@keyframes rotateXUp {
  0% {
    transform: rotateX(0deg);
  }
  100% {
    transform: rotateX(360deg);
  }
}

/* 下降时红色翻转 */
.rotatedDown {
  animation: rotateXDown 0.8s linear;
}
@keyframes rotateXDown {
  0% {
    transform: rotateX(0deg);
  }
  100% {
    transform: rotateX(-360deg);
  }
}
</style>

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值