Vue 3 抛物线小球动画

427 阅读2分钟

<Transition> 组件

<Transition> 是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:

  • 由 v-if 所触发的切换
  • 由 v-show 所触发的切换
  • 由特殊元素 <component> 切换的动态组件
  • 改变特殊的 key 属性

Transition 使用方式分为两种

  • 通过 css 来绘制对应的动画效果
  • 通过 transition 提供的回调函数钩子实现

以下是通过 css 来绘制对应的动画效果的示例:

<button @click="show = !show">Toggle</button>
<Transition> <p v-if="show">hello</p> </Transition>
/* 下面我们会解释这些 class 是做什么的 */
// 未给transition 命名所以默认是以v- 开头
// 如果命名就会以命名的名称开头
.v-enter-active, .v-leave-active 
{ transition: opacity 0.5s ease; }
.v-enter-from, .v-leave-to 
{ opacity: 0; }

通过 transition 提供的回调函数钩子实现

抛物线小球动画 需要结合 vue 提供的 transition 组件 暴露的三个钩子

  • @before-enter
  • @enter
  • @after-enter

因为vue中是将transition 放置于 nexttick 方法中执行所以你正常的通过v-show 操控元素显示会发现动画效果没有出现

需要在@enter中 执行 document.body.scrollHeight 触发浏览器重绘,才会出现动画效果

在获取元素位置时,可通过 getBoundingClientRect 方法获取元素, 对应视口的top bottom left right 所对应的值

使用 transition 钩子制作动画demo

<template>
  <div>
    <transition
      @before-enter="beforeDrop"
      @enter="dropping"
      @after-enter="afterDrop"
      @after-leave="afterLeave"
    >
      <div class="msg" v-if="showHidden">hello world</div>
    </transition>

    <button @click="switchShowHidden">切换</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const showHidden = ref(false);

function switchShowHidden() {
  showHidden.value = !showHidden.value;
}

function beforeDrop(ele: Element) {
  const curEle = ele as HTMLBodyElement;
  curEle.style.color = "red";
}
function afterLeave () {
  console.log('afterLeave')
}
function dropping(ele: Element, done: (...args: any) => any) {
  // 必须有当前行,否则动画不生效
  document.body.scrollHeight;
  
  const curEle = ele as HTMLBodyElement;
  curEle.style.color = "green";
  curEle.style.fontSize = "90px";
  
  // done 函数用于表示当前动画结束了
  done();
}
function afterDrop() {
  console.log("动画已经结束");
}
</script>

<style lang="scss" scoped>
.msg {
  transition: all 3s ease;
}
</style>

抛物线小球动画真实代码

  export interface BallType {
      showHidden: boolean;
      crrentTarget?: EventTarget;
  }
  static ball: Ref<BallType> = ref({ showHidden: false });
  static drop = (event: Event) => {
    ShopCart.ball.value.crrentTarget = event.target!;
    ShopCart.ball.value.showHidden = true;
  };
  // 动画之前
  static beforeBall(event: Element) {
    const curEle = event as HTMLBodyElement;
    const addBut = <HTMLBodyElement>ShopCart.ball.value.crrentTarget;
    // 可通过 `getBoundingClientRect` 方法获取元素的位置
    const addButEleReact = addBut.getBoundingClientRect();
    const x = addButEleReact.left - 35;
    const y = -(window.innerHeight - addButEleReact.top - 22);
    const inner = curEle.getElementsByClassName("inner")[0] as HTMLBodyElement;

    curEle.style.transform = `translate3d(0, ${y}px, 0)`;
    inner.style.transform = `translate3d(${x}px, 0, 0)`;
  }
  // 动画进行时
  static balling(event: Element, done: (...args: any) => any) {
    const curEle = event as HTMLBodyElement;
    const inner = curEle.getElementsByClassName("inner")[0] as HTMLBodyElement;
    document.body.scrollHeight;
    curEle.style.transform = "translate3d(0, 0, 0)";
    inner.style.transform = "translate3d(0, 0, 0)";
    done();
  }
  // 动画之后
  static afterBall() {
    ShopCart.ball.value.showHidden = false;
    ShopCart.ball.value.crrentTarget = undefined;
  }

经常容易遇见的小问题,相同的标签名切换时没有动画效果

比如这两个都是<p>标签,就会出现问题

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
    <p v-else>你好</p>
  </transition>
</div>

setup(){ const show = ref(true) }

问题是怎么产生的呢?

在vue中当有相同标签名的元素切换时,需要通过key属性设置唯一的值来标记,让Vue区分他们,否则Vue为了效率和提高性能,只会替换相同标签内部的内容。不会创建新的元素,即使在计算上没有必要,但是给在 组件中的多个元素设置key是一个更好的实践

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show" key="1">hello</p>
    <p v-else  key="2" >你好</p>
  </transition>
</div>