第5章:过渡与动画

第13节:JavaScript钩子与列表过渡

5.13.1 JavaScript钩子函数

Vue的过渡系统提供了一系列JavaScript钩子函数,可以在过渡的不同阶段执行自定义逻辑。这些钩子函数可以与CSS过渡结合使用,也可以单独使用。

钩子函数列表

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
>
  <p v-if="show">hello</p>
</transition>

钩子函数详解

  1. **before-enter**:进入过渡前触发

    • 此时元素尚未插入到DOM
    • 用途:可以设置元素的初始状态
  2. **enter**:进入过渡开始时触发

    • 此时元素已插入到DOM
    • 必须调用done回调函数来结束过渡,否则过渡将永远不会结束
    • 用途:可以使用JavaScript动画库来实现复杂的动画效果
  3. **after-enter**:进入过渡完成后触发

    • 此时过渡已结束
    • 用途:可以执行动画完成后的清理工作
  4. **enter-cancelled**:进入过渡被取消时触发

    • 仅在进入过渡尚未完成时触发
    • 用途:可以执行取消动画的清理工作
  5. **before-leave**:离开过渡前触发

    • 此时元素仍在DOM中
    • 用途:可以设置元素的初始状态
  6. **leave**:离开过渡开始时触发

    • 必须调用done回调函数来结束过渡
    • 用途:可以使用JavaScript动画库来实现复杂的动画效果
  7. **after-leave**:离开过渡完成后触发

    • 此时元素已从DOM中移除
    • 用途:可以执行动画完成后的清理工作
  8. **leave-cancelled**:离开过渡被取消时触发

    • 仅在v-show过渡中可用
    • 用途:可以执行取消动画的清理工作

示例:使用JavaScript钩子实现动画

<template>
  <div>
    <button @click="show = !show">
      切换显示
    </button>
    <transition
      @before-enter="beforeEnter"
      @enter="enter"
      @leave="leave"
    >
      <p v-if="show" ref="el">hello</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    beforeEnter(el) {
      // 初始状态:透明且向上偏移
      el.style.opacity = 0
      el.style.transform = 'translateY(-20px)'
    },
    enter(el, done) {
      // 动画过程:使用requestAnimationFrame实现平滑动画
      let duration = 500 // 动画持续时间
      let start = null
      
      function animate(timestamp) {
        if (!start) start = timestamp
        let progress = timestamp - start
        let percent = Math.min(progress / duration, 1)
        
        // 使用easeOutQuad缓动函数
        let eased = 1 - Math.pow(1 - percent, 2)
        
        el.style.opacity = eased
        el.style.transform = `translateY(${(1 - eased) * -20}px)`
        
        if (percent < 1) {
          requestAnimationFrame(animate)
        } else {
          done() // 动画完成,调用done回调
        }
      }
      
      requestAnimationFrame(animate)
    },
    leave(el, done) {
      // 离开动画:使用setTimeout实现简单动画
      el.style.transition = 'opacity 0.5s, transform 0.5s'
      el.style.opacity = 0
      el.style.transform = 'translateY(20px)'
      
      setTimeout(() => {
        done() // 动画完成,调用done回调
      }, 500)
    }
  }
}
</script>

5.13.2 使用Velocity.js或GSAP库

Vue的JavaScript钩子可以与第三方动画库(如Velocity.js或GSAP)结合使用,实现更复杂的动画效果。

示例:使用GSAP实现动画

<template>
  <div>
    <button @click="show = !show">
      切换显示
    </button>
    <transition
      @enter="enter"
      @leave="leave"
    >
      <p v-if="show">hello</p>
    </transition>
  </div>
</template>

<script>
import gsap from 'gsap'

export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    enter(el, done) {
      // 使用GSAP实现进入动画
      gsap.fromTo(el, 
        { opacity: 0, y: -20 }, // 初始状态
        { opacity: 1, y: 0, duration: 0.5, onComplete: done } // 结束状态
      )
    },
    leave(el, done) {
      // 使用GSAP实现离开动画
      gsap.to(el, 
        { opacity: 0, y: 20, duration: 0.5, onComplete: done } // 结束状态
      )
    }
  }
}
</script>

示例:使用Velocity.js实现动画

<template>
  <div>
    <button @click="show = !show">
      切换显示
    </button>
    <transition
      @enter="enter"
      @leave="leave"
    >
      <p v-if="show">hello</p>
    </transition>
  </div>
</template>

<script>
import Velocity from 'velocity-animate'

export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    enter(el, done) {
      // 使用Velocity.js实现进入动画
      Velocity(el, {
        opacity: [1, 0],
        translateY: [0, -20]
      }, {
        duration: 500,
        complete: done
      })
    },
    leave(el, done) {
      // 使用Velocity.js实现离开动画
      Velocity(el, {
        opacity: 0,
        translateY: 20
      }, {
        duration: 500,
        complete: done
      })
    }
  }
}
</script>

5.13.3 列表过渡&lt;transition-group&gt;

&lt;transition-group&gt;组件用于实现列表项的过渡效果,它与&lt;transition&gt;组件的用法类似,但有一些特殊的注意事项。

基本用法

<template>
  <div>
    <button @click="addItem">添加项目</button>
    <button @click="removeItem">移除项目</button>
    <transition-group name="list" tag="ul">
      <li v-for="item in items" :key="item.id">
        {{ item.text }}
      </li>
    </transition-group>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, text: '项目1' },
        { id: 2, text: '项目2' },
        { id: 3, text: '项目3' }
      ],
      nextId: 4
    }
  },
  methods: {
    addItem() {
      this.items.push({
        id: this.nextId++,
        text: `项目${this.nextId - 1}`
      })
    },
    removeItem() {
      this.items.shift()
    }
  }
}
</script>

<style>
.list-enter-active, .list-leave-active {
  transition: all 0.5s ease;
}
.list-enter-from, .list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
</style>

&lt;transition-group&gt;的特点

  1. 必须提供key属性:每个列表项都必须有唯一的key属性
  2. **默认渲染为&lt;span&gt;**:可以使用tag属性指定渲染的元素
  3. 支持move过渡:可以添加v-move类名来实现列表项移动时的过渡效果
  4. 不支持mode属性:列表过渡中没有过渡模式

添加move过渡

<transition-group name="list" tag="ul">
  <li v-for="item in items" :key="item.id">
    {{ item.text }}
  </li>
</transition-group>

<style>
.list-enter-active, .list-leave-active, .list-move {
  transition: all 0.5s ease;
}
.list-enter-from, .list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
.list-leave-active {
  position: absolute;
}
</style>

5.13.4 列表的排序过渡与FLIP技术

当列表项的顺序发生变化时,Vue使用FLIP技术来实现平滑的过渡效果。FLIP是"First, Last, Invert, Play"的缩写,是一种实现高性能动画的技术。

FLIP技术原理

  1. First:记录元素的初始位置和大小
  2. Last:执行DOM操作,记录元素的最终位置和大小
  3. Invert:计算初始状态和最终状态之间的差异,反转元素的位置和大小
  4. Play:使用过渡动画将元素从反转状态恢复到最终状态

示例:实现可排序列表

<template>
  <div>
    <h3>可排序列表</h3>
    <transition-group name="flip-list" tag="ul">
      <li
        v-for="item in items"
        :key="item.id"
        @click="moveItem(item)"
      >
        {{ item.text }}
      </li>
    </transition-group>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, text: '项目1' },
        { id: 2, text: '项目2' },
        { id: 3, text: '项目3' },
        { id: 4, text: '项目4' },
        { id: 5, text: '项目5' }
      ]
    }
  },
  methods: {
    moveItem(item) {
      // 随机移动项目到新位置
      const currentIndex = this.items.indexOf(item)
      const newIndex = Math.floor(Math.random() * this.items.length)
      
      // 从原位置移除
      this.items.splice(currentIndex, 1)
      // 添加到新位置
      this.items.splice(newIndex, 0, item)
    }
  }
}
</script>

<style>
.flip-list-move {
  transition: transform 0.5s ease;
}
.flip-list-enter-active, .flip-list-leave-active {
  transition: opacity 0.5s ease;
}
.flip-list-enter-from, .flip-list-leave-to {
  opacity: 0;
}
.flip-list-leave-active {
  position: absolute;
}
</style>

使用Sortable.js实现拖拽排序

<template>
  <div>
    <h3>拖拽排序列表</h3>
    <transition-group name="flip-list" tag="ul" ref="sortableList">
      <li
        v-for="item in items"
        :key="item.id"
        class="sortable-item"
      >
        {{ item.text }}
      </li>
    </transition-group>
  </div>
</template>

<script>
import Sortable from 'sortablejs'

export default {
  data() {
    return {
      items: [
        { id: 1, text: '项目1' },
        { id: 2, text: '项目2' },
        { id: 3, text: '项目3' }
      ]
    }
  },
  mounted() {
    // 初始化Sortable.js
    Sortable.create(this.$refs.sortableList, {
      animation: 150,
      onEnd: (evt) => {
        // 更新数据顺序
        const item = this.items.splice(evt.oldIndex, 1)[0]
        this.items.splice(evt.newIndex, 0, item)
      }
    })
  }
}
</script>

<style>
.flip-list-move {
  transition: transform 0.5s ease;
}
.sortable-item {
  cursor: move;
  padding: 10px;
  margin: 5px 0;
  background-color: #f0f0f0;
  border-radius: 4px;
}
</style>

总结

Vue的JavaScript钩子函数和列表过渡功能为我们提供了实现复杂动画效果的能力。通过结合JavaScript钩子和第三方动画库,我们可以实现各种精美的动画效果。

列表过渡使用&lt;transition-group&gt;组件,结合FLIP技术,可以实现列表项添加、移除和排序时的平滑过渡效果。这对于创建交互式列表非常有用,如可排序列表、动态添加/移除项目等。

在下一章中,我们将学习Vue 3的组合式API,这是Vue 3的核心特性之一。

« 上一篇 CSS过渡与动画 下一篇 » 响应式基础