大家好,我是前端架构师,关注微信公众号【程序员大卫】免费领取前端精品资料。
背景
在一些场景中,我们需要给页面元素添加加载效果,例如在数据请求时显示一个旋转的加载动画。Vue 提供了自定义指令的功能,让我们能够直接在 HTML 模板中应用加载效果,而不需要修改大量的组件代码。
通过本文的教程,你将会学会如何创建一个简单的 v-loading 指令,控制加载动画的显示,并能灵活修改加载动画的颜色和大小。
目录结构如下
src/
├── App.vue
├── main.ts
├── directive/
│ ├── index.ts # 注册所有自定义指令
│ └── loading/
│ ├── index.ts # v-loading 指令逻辑
│ ├── createLoading.ts # 创建/更新/销毁 loading 实例
│ ├── Loading.vue # loading 动画组件
│ └── types.ts # 类型定义
步骤一:准备工作
首先,我们需要创建一个基本的 Vue 3 项目结构,并在其中实现一个加载动画,需要准备两个按钮:
- 一个用来切换加载动画的
显示/隐藏 - 一个用来切换整个组件的
销毁与否
<!-- App.vue -->
<script setup lang="ts">
import { ref } from "vue";
const loading = ref(true); // loading 用来控制加载动画显示
const isVisible = ref(true); // isVisible 用来控制组件销毁
</script>
<template>
<!-- 控制加载动画的显示/隐藏 -->
<button @click="loading = !loading">toggle visible</button>
<!-- 控制组件的销毁 -->
<button @click="isVisible = !isVisible">toggle destroy</button>
<!-- 显示加载动画的区域 -->
<div
v-if="isVisible"
v-loading="loading"
loading-color="red"
loading-size="60"
class="section"
></div>
</template>
<style scoped>
.section {
width: 200px;
height: 200px;
border: 1px solid red;
margin-top: 20px;
}
</style>
步骤二:创建 v-loading 自定义指令
接下来,我们需要实现 v-loading 自定义指令的逻辑。这个指令将会根据传入的参数控制加载动画的显示状态,并根据元素的属性来动态更新动画的大小和颜色。
首先,我们定义一个 createLoading 函数来创建加载动画,并通过指令的生命周期钩子来管理它:
1. createLoading.ts 文件
/**
* 创建、更新和销毁 v-loading 的实例
* @file directive/loading/createLoading.ts
*/
import { defineComponent, h, reactive, render } from "vue";
import type { LoadingProps } from "./types";
import Loading from "./Loading.vue";
export function createLoading(
props: Partial<LoadingProps>,
target: HTMLElement
) {
const data = reactive({
...props,
});
const Comp = defineComponent({
render: () => h(Loading, data),
});
// 创建虚拟节点
const vNode = h(Comp);
// 将虚拟节点挂载到容器上
render(vNode, target);
return {
update: (newProps: Partial<LoadingProps>) => Object.assign(data, newProps),
destroy: () => render(null, target),
};
}
2. Loading.vue 文件
Loading.vue 组件负责展示加载动画。我们通过 size 和 color 属性控制加载动画的大小和颜色。
⚠️注:Loading 动画相关代码可以从下面网站拷贝。
<!--
loading 动画组件
@file directive/loading/Loading.vue
-->
<script setup lang="ts">
import { computed } from "vue";
import type { LoadingProps } from "./types";
// 定义组件的 props,支持传递大小、颜色和是否显示
const props = withDefaults(defineProps<Partial<LoadingProps>>(), {
isVisible: true,
size: 30,
color: "#25b09b",
});
// 计算加载动画的实际大小(单位:px)
const sizePx = computed(() => props.size + "px");
</script>
<template>
<div class="loader-box" v-if="isVisible">
<div class="loader"></div>
</div>
</template>
<style scoped>
.loader-box {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
.loader {
width: v-bind("sizePx");
height: v-bind("sizePx");
background: v-bind("color");
padding: 4px;
border-radius: 50%;
--_m: conic-gradient(#0000 10%, #000), linear-gradient(#000 0 0) content-box;
-webkit-mask: var(--_m);
mask: var(--_m);
-webkit-mask-composite: source-out;
mask-composite: subtract;
animation: l3 1s infinite linear;
}
@keyframes l3 {
to {
transform: rotate(1turn);
}
}
</style>
3. types.ts 文件
为了让 Loading.vue 组件的属性更清晰,我们定义一个类型 LoadingProps 来描述它的属性结构。
/**
* 类型定义
* @file directive/loading/type.ts
*/
export interface LoadingProps {
isVisible: boolean;
size: number;
color: string;
}
4. 定义 loading 指令核心逻辑
/**
* v-loading 指令逻辑
* @file directive/loading/index.ts
*/
import type { Directive, DirectiveBinding } from "vue";
import { createLoading } from "./createLoading";
interface El extends HTMLElement {
__loadingInstance: ReturnType<typeof createLoading>;
}
type Binding = DirectiveBinding<boolean>;
// 获取指令传递的参数,包含动画大小、颜色和是否可见
const getProps = (el: El, binding: Binding) => {
const size = el.getAttribute("loading-size");
const color = el.getAttribute("loading-color");
return {
size: size === null ? undefined : Number(size),
color: color === null ? undefined : color,
isVisible: binding.value,
};
};
export const loading: Directive = {
mounted(el: El, binding: Binding) {
console.log("mounted");
const props = getProps(el, binding);
const instance = createLoading(props, el);
el.__loadingInstance = instance;
},
updated(el: El, binding: Binding) {
console.log("updated");
const props = getProps(el, binding);
el.__loadingInstance.update(props);
},
unmounted(el: El) {
console.log("unmounted");
el.__loadingInstance.destroy();
},
};
步骤三:注册指令
在 Vue 3 中,我们需要在应用中注册自定义指令。我们将 v-loading 指令注册到 Vue 应用中,以便可以在模板中直接使用。
1. setupDirective.ts 文件
/**
* 注册所有自定义指令
* @file directive/index.ts
*/
import type { App } from "vue";
import { loading } from "./loading";
// 用来在 Vue 应用中注册指令的函数
export function setupDirective(app: App<Element>) {
app.directive("loading", loading);
}
2. 主应用入口 main.ts
在应用的入口文件中,我们导入并调用 setupDirective 来注册指令。
import { createApp } from "vue";
import App from "./App.vue";
import { setupDirective } from "./directive";
// 创建 Vue 实例
const app = createApp(App);
// 注册自定义指令
setupDirective(app);
// 挂载应用
app.mount("#app");
总结
通过这三个简单的步骤,我们成功实现了一个 v-loading 自定义指令,能够在 Vue 应用中控制加载动画的显示与隐藏。通过自定义指令,我们不仅可以在模板中方便地添加功能,还能灵活控制加载动画的样式。
希望你能通过这篇文章理解如何使用 Vue 自定义指令来提升你的开发效率!
源码地址: