avatar

麦兜的小站

MDO.INK

  • 首页
  • 随笔
  • 知识库
  • 归档
  • 动态
  • 标签
  • 关于
Home Vue3 directives自定义指令
文章

Vue3 directives自定义指令

Posted 2025-02-4 Updated 2025-02- 4
By power 已删除用户
14~18 min read

使用过 element-ui 组件应该都使用过 v-loading 指令来过渡数据加载时间。它是一个常用且非常实用的功能。接下来使用 directives API 实现一个 v-load 自定义指令来实现 v-loading 效果。

1.实现 loading 效果

使用 css 实现 loading 效果

.loader-parent--relative {
    position: relative;
}

.loader-mask {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: rgba(255, 255, 255, 0.9);
    z-index: 10;
}

.loader {
    width: 50px;
    aspect-ratio: 1;
    border-radius: 50%;
    border: 4px solid #1ed760;
    animation: l20-1 0.8s infinite linear alternate, l20-2 1.6s infinite linear;
}
@keyframes l20-1 {
    0% {
        clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%);
    }
    12.5% {
        clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0%);
    }
    25% {
        clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100%);
    }
    50% {
        clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%);
    }
    62.5% {
        clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%);
    }
    75% {
        clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100%);
    }
    100% {
        clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100%);
    }
}
@keyframes l20-2 {
    0% {
        transform: scaleY(1) rotate(0deg);
    }
    49.99% {
        transform: scaleY(1) rotate(135deg);
    }
    50% {
        transform: scaleY(-1) rotate(0deg);
    }
    100% {
        transform: scaleY(-1) rotate(-135deg);
    }
}

.fade-in {
    animation: fade-in 0.3s ease;
}

.fade-out {
    animation: fade-out 0.3s ease;
}

@keyframes fade-in {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

@keyframes fade-out {
    0% {
        opacity: 1;
    }

    100% {
        opacity: 0;
    }
}

.loader-mask 是 loading 的遮罩层,.loading 才是主要的 loading 主体,利用动画来实现 v-loading 一样的圆圈 loading。.fade-in 和.fade-out 分别是显示和隐藏的过渡动画。

2. 实现 v-load 指令

// directives/load.ts
import { App } from 'vue'

const sleep = async (s: number) => await new Promise(resolve => setTimeout(() => resolve(), s))

const createMask = () => {
    const loadingMask = document.createElement('div')
    loadingMask.classList.add('loader-mask')

    const spinner = document.createElement('div')
    spinner.classList.add('loader')
    loadingMask.appendChild(spinner)

    return loadingMask
}

export const loadDirective = {
    // 指令的生命周期钩子函数:更新时
    async updated(el: HTMLElement, binding: any, vnode, prevVnode) {
        if (binding.oldValue === binding.value) return
        if (binding.value) {
            const loadingMask = createMask()
            loadingMask.classList.add('fade-in')

            el.classList.add('loader-parent--relative')
            el.appendChild(loadingMask)

            await sleep(300).then(() => loadingMask.classList.remove('fade-in'))
        } else {
            const loadingMask = el.querySelector('.loader-mask')
            loadingMask?.classList.add('fade-out')
            await sleep(300)
                .then(() => el.removeChild(loadingMask))
                .then(() => el.classList.remove('loader-parent--relative'))
        }
    }
}

export default {
    install(app: App) {
        app.directive('load', loadDirective)
    }
}

loadDirective 对象就是定义自定义指令的配置。

| 钩子函数 | 描述 | | --- | --- | | created(el, binding, vnode){} | 在绑定元素的 attribute 前或事件监听器应用前调用 | | beforeMount(el, binding, vnode){} | 在元素被插入到 DOM 前调用 | | mounted(el, binding, vnode){} | 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用 | | beforeUpdate(el, binding, vnode, prevVnode){} | 绑定元素的父组件更新前调用 | | updated(el, binding, vnode,prevVnode){} | 在绑定元素的父组件及他自己的所有子节点都更新后调用 | | beforeUnmount(el, binding, vnode){} | 绑定元素的父组件卸载前调用 | | unmounted(el, binding, vnode){} | 绑定元素的父组件卸载后调用 | | 参数 | 描述 | | --- | --- | | el | 指令绑定的元素 | | binding | 对象 | | -value | 传递给指令的值 | | -oldValue | 之前的值,仅在 beforeUpdate 和 updated 中可用。 | | -arg | 传递给指令的参数 (如果有的话)。 | | -modifiers | 一个包含修饰符的对象 (如果有的话)。 | | -instance | 使用该指令的组件实例。 | | -dir | 指令的定义对象。 | | vnode | 代表绑定元素的底层 VNode。 | | prevVnode | 代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。 |

实现逻辑很简单,v-load 我们只需要用到 updated 钩子。createMask 方法创建遮罩层容器和 loading 主体,分别加上 loader-mask 和 loader 类。 我们只需要在 updated 中判断 binding.value,如果是 true,为 createMask 返回的 el 添加显示过渡 class,给绑定元素添加 loader-parent–relative 类,然后将遮罩层容器添加到绑定元素上即可。 如果是 false,在绑定元素上查找遮罩层元素,为它添加隐藏过渡 class,在等待动画执行时间后移除遮罩层容器,然后移除绑定元素是相对定位 loader-parent–relative 类即可。

因为 updated 在父组件和子组件更新时都会调用,即如果页面上使用了 x 次指令,那么在你修改一次值时就会调用 x 次 updated 钩子,所以需要 if (binding.oldValue === binding.value) return 判断是否是值变动的元素。

还需要注意,因为只使用了 updated 钩子,如果在一开始就赋值 true,那么不会有 loading 效果,因为没有在 created 钩子中做处理。

3. 全局注册指令和局部注册指令

全局注册

因为在 load.ts 中定义了一个有 install 方法的对象,它已经通过 app 注册了指令,所以我们在 main.ts 中使用 app.use 即可全局注册。

... other code
const app = createApp(App)

app.use(loadDirective)
... other code

局部注册

Vue3 在 setup 语法糖中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令,我们导入定义自定义指令的 loadDirective 配置即可。

// xxx.vue
<script setup lang="ts">
import { loadDirective } from 'directives/load'
const vLoad = loadDirective
</script>

4. 指令使用

<template>
    <div class="w-20 h-20 bg-black" v-load="loading">loading</div>
    <button @click="change">切换loading</button>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const loading = ref<boolean>(false)

const change = () => {
    loading.value = !loading.value
}
</script>

v-loading 效果已经实现,如果需要使用其他效果也可参考该文章。

前端
License:  CC BY 4.0
Share

Further Reading

Jun 25, 2025

Vue实现顶部导航跟随页面联动效果

需求背景 最近也是应我一位同学的要求,给他的公司制作一个官网,那也就是拾起了一些基础的知识,搜索了一些其它的文章有实现这种效果的,但大多导航都是在侧边,然后点击导航对应的部分内容滑到最上面,跟我这次设...

Jun 20, 2025

Share.js - 一键分享到微博、QQ空间、QQ好友、微信、腾讯微博、豆瓣、Facebook、Twitter、Linkedin、Google+、点点

一键分享到微博、QQ空间、QQ好友、微信、腾讯微博、豆瓣、Facebook、Twitter、Linkedin、Google+、点点等社交网站,使用字体图标。 有两种安装方式: 使用npm npmi...

Jun 19, 2025

node express实现热更新

在使用node进行开发的时候,每次修改文件,都需要重启express服务很麻烦 我们可以使用nodemon这个库,修改文件后可以自动重启express服务。 安装: npminstall--save-...

OLDER

SSH远程免密登录的两种方式

NEWER

pushoo.js | pushoo

Recently Updated

  • 如何实现接口幂等性
  • 10个npm工具包
  • How to set up PHP7.4 on MacOS.
  • Automa:一键自动化,网页数据采集与工作流程优化专家Automa:解锁自动化
  • Mac 下用 brew 搭建 LNMP

Trending Tags

thinkphp clippings

Contents

©2025 麦兜的小站. Some rights reserved.

Using the Halo theme Chirpy