效果
效果:
基础结构
- 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>