Featured image of post 2023 手把手教你 Vue3 组件上下和左右切换动画效果

2023 手把手教你 Vue3 组件上下和左右切换动画效果

效果

效果:2023-05-09-vue3_transition

基础结构

  • 1、创建 A 和 B 两个组件,通过切换这两个组件实现组件的“离开”“进入”状态。
    • 如果想创建多个组件也是可以的,后面会说。
  • HTML 结构
    • class="is-plain",判断按钮当前是否被选中
    • @click="updateFade({name, index,'Y'}):点击按钮时传递 name 和 index 索引,以及当前是 X 轴水平动画还是 Y轴。
  • TS 结构——接口
    • IUlList:定义基础结构
      • name 是唯一标识
      • comp:是组件,可以不填。
      • status:当前是否被选中
    • ICurrentIndex:记录当前 X 和 Y 轴 被选中的索引值,后续通过索引改变 status
    • EToggleFade:动画效果,Left 是指向左滑动。
  • TS 结构——方法
    • toggleFade:需要使用的动画效果,点击按钮就会改变他的值,然后实现动画。
    • updateFade:后面说
<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="80px">
        <el-button
          v-for="(item, index) in asideList"
          :key="item.name"
          type="primary"
          :class="{ 'is-plain': !item.status }"
          @click="updateFade({ name: item.name, index }, 'Y')"
        >
          {{ item.name }}
        </el-button>
      </el-aside>
      <el-container>
        <el-header
          ><el-button
            v-for="(item, index) in headerList"
            :key="item.name"
            type="primary"
            :class="{ 'is-plain': !item.status }"
            @click="updateFade({ name: item.name, index }, 'X')"
          >
            {{ item.name }}
          </el-button></el-header
        >
        <el-main style="position: relative">
          <transition :name="toggleFade">
            <component :is="currentTab" />
          </transition>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script setup lang="ts">
  import type { DefineComponent } from "vue";
  import { reactive, ref, shallowRef } from "vue";
  import A from "./A.vue";
  import B from "./B.vue";

  interface IUlList {
    name: string;
    comp?: DefineComponent;
    status: boolean;
  }

  interface ICurrentIndex {
    x: number;
    y: number;
  }

  enum EToggleFade {
    LEFT = "toggle-fade-left",
    RIGHT = "toggle-fade-right",
    TOP = "toggle-fade-top",
    BOTTOM = "toggle-fade-bottom",
  }

  // 现在显示的组件
  let currentTab = shallowRef(A);

  // name 必须是唯一的, 通过 name 标识
  // name 叫什么都可以, 随便取名
  const headerList = reactive<IUlList[]>([
    { name: "header-1", status: true },
    { name: "header-2", status: false },
    { name: "header-3", status: false },
  ]);

  const asideList = reactive<IUlList[]>([
    { name: "aside-1", status: true },
    { name: "aside-2", status: false },
    { name: "aside-3", status: false },
    { name: "aside-4", status: false },
  ]);

  // left是指: 往左滑动, 参考自己的手机屏幕滑动
  let toggleFade = ref<EToggleFade>(EToggleFade.LEFT);

  // 参数1: item.name, item.index
  // 参数2: X/Y, 是需要水平动画还是垂直动画
  let updateFade = (
    item: { name: string; index: number },
    direction: "X" | "Y"
  ): void => {};
</script>

<style scoped lang="scss">
  .common-layout {
    width: 300px;

    .el-button {
      margin: 0;
    }

    .el-header {
      display: flex;
      flex-grow: 0;
      flex-shrink: 0;
      padding: 0;
      height: 40px;
    }
  }
</style>

动画效果的实现

  • 我们从 if 语句开始
    • currentIndex.x < _index:如果当前点击的按钮索引 大于 之前的,则判断为 向左滑动 LEFT
    • 然后就是更新列表状态和 活跃的索引
  • CSS
    • 有个地方需要解锁下,关于最后为什么用 absolute
      • 主要作用是使得 离开状态 的组件不占用空间,不然就会同时出现两个组件互相占用空间(互相顶着)。
      • 或者你可以用 out-in 也可以。这部分在 官网 上是有解释的。
    • 其他其实没什么好说的,看不懂就去看 Vue3 在官方文档。
// 参数1: item.name, item.index
// 参数2: X/Y, 是需要水平动画还是垂直动画
let updateFade = (item: { name: string; index: number }, direction: 'X' | 'Y'): void => {
  // 组件切换
  currentTab.value = currentTab.value == A ? B : A
  // currentTab.value = headerList[item.index].comp

  // 当前点击组件的name和index
  let _name = item.name
  let _index = item.index

  if (direction === 'X') {
    toggleFade.value = currentIndex.x < _index ? EToggleFade.LEFT : EToggleFade.RIGHT

    // 更新列表状态
    headerList[_index].status = true
    headerList[currentIndex.x].status = false

    // 更新当前活跃的索引
    currentIndex.x = _index
  } else if (direction === 'Y') {
    toggleFade.value = currentIndex.y < _index ? EToggleFade.TOP : EToggleFade.BOTTOM

    // 更新列表状态
    asideList[_index].status = true
    asideList[currentIndex.y].status = false

    // 更新当前活跃的索引
    currentIndex.y = _index
  }
}
.toggle-fade-right-enter-from,
.toggle-fade-left-leave-to {
  transform: translateX(-100%);
}

.toggle-fade-right-leave-to,
.toggle-fade-left-enter-from {
  transform: translateX(100%);
}

.toggle-fade-top-enter-from,
.toggle-fade-bottom-leave-to {
  transform: translateY(100%);
}

.toggle-fade-top-leave-to,
.toggle-fade-bottom-enter-from {
  transform: translateY(-100%);
}

.toggle-fade-left-leave-active,
.toggle-fade-right-leave-active,
.toggle-fade-left-enter-active,
.toggle-fade-right-enter-active,
.toggle-fade-top-leave-active,
.toggle-fade-bottom-leave-active,
.toggle-fade-top-enter-active,
.toggle-fade-bottom-enter-active {
  transition: all 0.5s ease-out;
}

// 确保组件是连着的
.toggle-fade-left-leave-active,
.toggle-fade-right-leave-active,
.toggle-fade-top-leave-active,
.toggle-fade-bottom-leave-active {
  position: absolute;
}

多个组件

  • 只需要对 headerList 每个对象添加以一个 comp
  • 然后在改下切换的方式。
import A from "./A.vue";
import B from "./B.vue";

const headerList = reactive<IUlList[]>([
  { name: "header-1", comp: A, status: true },
  { name: "header-2", comp: B, status: false },
  { name: "header-3", comp: A, status: false },
]);

// 参数1: item.name, item.index
// 参数2: X/Y, 是需要水平动画还是垂直动画
let updateFade = (
  item: { name: string; index: number },
  direction: "X" | "Y"
): void => {
  // 组件切换
  //currentTab.value = currentTab.value == A ? B : A
  currentTab.value = headerList[item.index].comp;

  // ......
};

完整代码

<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="80px">
        <el-button
          v-for="(item, index) in asideList"
          :key="item.name"
          type="primary"
          :class="{ 'is-plain': !item.status }"
          @click="updateFade({ name: item.name, index }, 'Y')"
        >
          {{ item.name }}
        </el-button>
      </el-aside>
      <el-container>
        <el-header
          ><el-button
            v-for="(item, index) in headerList"
            :key="item.name"
            type="primary"
            :class="{ 'is-plain': !item.status }"
            @click="updateFade({ name: item.name, index }, 'X')"
          >
            {{ item.name }}
          </el-button></el-header
        >
        <el-main style="position: relative">
          <transition :name="toggleFade">
            <component :is="currentTab" />
          </transition>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script setup lang="ts">
  import type { DefineComponent } from "vue";
  import { reactive, ref, shallowRef } from "vue";
  import A from "./A.vue";
  import B from "./B.vue";

  interface IUlList {
    name: string;
    comp?: DefineComponent;
    status: boolean;
  }

  interface ICurrentIndex {
    x: number;
    y: number;
  }

  enum EToggleFade {
    LEFT = "toggle-fade-left",
    RIGHT = "toggle-fade-right",
    TOP = "toggle-fade-top",
    BOTTOM = "toggle-fade-bottom",
  }

  // 现在显示的组件
  let currentTab = shallowRef(A);

  // name 必须是唯一的, 通过 name 标识
  // name 叫什么都可以, 随便取名
  const headerList = reactive<IUlList[]>([
    { name: "header-1", status: true },
    { name: "header-2", status: false },
    { name: "header-3", status: false },
  ]);

  const asideList = reactive<IUlList[]>([
    { name: "aside-1", status: true },
    { name: "aside-2", status: false },
    { name: "aside-3", status: false },
    { name: "aside-4", status: false },
  ]);

  // 记录当前活跃的索引
  const currentIndex = reactive<ICurrentIndex>({
    x: 0,
    y: 0,
  });

  // left是指: 往左滑动, 参考自己的手机屏幕滑动
  let toggleFade = ref<EToggleFade>(EToggleFade.LEFT);

  // 参数1: item.name, item.index
  // 参数2: X/Y, 是需要水平动画还是垂直动画
  let updateFade = (
    item: { name: string; index: number },
    direction: "X" | "Y"
  ): void => {
    // 组件切换
    currentTab.value = currentTab.value == A ? B : A;
    // currentTab.value = headerList[item.index].comp

    // 当前点击组件的name和index
    let _name = item.name;
    let _index = item.index;

    if (direction === "X") {
      toggleFade.value =
        currentIndex.x < _index ? EToggleFade.LEFT : EToggleFade.RIGHT;

      // 更新列表状态
      headerList[_index].status = true;
      headerList[currentIndex.x].status = false;

      // 更新当前活跃的索引
      currentIndex.x = _index;
    } else if (direction === "Y") {
      toggleFade.value =
        currentIndex.y < _index ? EToggleFade.TOP : EToggleFade.BOTTOM;

      // 更新列表状态
      asideList[_index].status = true;
      asideList[currentIndex.y].status = false;

      // 更新当前活跃的索引
      currentIndex.y = _index;
    }
  };
</script>

<style scoped lang="scss">
  .common-layout {
    width: 300px;

    .el-button {
      margin: 0;
    }

    .el-header {
      display: flex;
      flex-grow: 0;
      flex-shrink: 0;
      padding: 0;
      height: 40px;
    }
  }

  .toggle-fade-right-enter-from,
  .toggle-fade-left-leave-to {
    transform: translateX(-100%);
  }

  .toggle-fade-right-leave-to,
  .toggle-fade-left-enter-from {
    transform: translateX(100%);
  }

  .toggle-fade-top-enter-from,
  .toggle-fade-bottom-leave-to {
    transform: translateY(100%);
  }

  .toggle-fade-top-leave-to,
  .toggle-fade-bottom-enter-from {
    transform: translateY(-100%);
  }

  .toggle-fade-left-leave-active,
  .toggle-fade-right-leave-active,
  .toggle-fade-left-enter-active,
  .toggle-fade-right-enter-active,
  .toggle-fade-top-leave-active,
  .toggle-fade-bottom-leave-active,
  .toggle-fade-top-enter-active,
  .toggle-fade-bottom-enter-active {
    transition: all 0.5s ease-out;
  }

  // 确保组件是连着的
  .toggle-fade-left-leave-active,
  .toggle-fade-right-leave-active,
  .toggle-fade-top-leave-active,
  .toggle-fade-bottom-leave-active {
    position: absolute;
  }
</style>
Licensed under CC BY-NC-SA 4.0
本博客已稳定运行
发表了53篇文章 · 总计28.17k字
使用 Hugo 构建
主题 StackJimmy 设计