实现全局自定义loading指令

以下是分别针对 Vue2 和 Vue3 的 v-loading 全局自定义指令实现:

Vue2 实现

// src/directives/loading.js

export default {

// 指令绑定到元素时触发

bind(el, binding) {

this.init(el, binding)

},

// 元素插入 DOM 时触发

inserted(el, binding) {

this.update(el, binding)

},

// 指令值更新时触发

update(el, binding) {

if (binding.value) {

this.showLoading(el, binding)

} else {

this.hideLoading(el)

}

},

// 指令卸载时触发

unbind(el) {

this.destroy(el)

},

// 初始化加载层

init(el, binding) {

// 创建加载层容器

this.loadingDiv = document.createElement('div')

this.loadingDiv.className = 'v-loading-container'

this.loadingDiv.style.position = 'absolute'

this.loadingDiv.style.inset = '0'

this.loadingDiv.style.backgroundColor = 'rgba(255,255,255,0.8)'

this.loadingDiv.style.display = 'none'

this.loadingDiv.style.justifyContent = 'center'

this.loadingDiv.style.alignItems = 'center'

this.loadingDiv.style.pointerEvents = 'none'

复制代码
// 创建加载动画元素
const spinner = document.createElement('div')
spinner.className = 'v-loading-spinner'
spinner.style.border = '4px solid #2c3e50'
spinner.style.borderTopColor = 'transparent'
spinner.style.borderRadius = '50%'
spinner.style.width = '24px'
spinner.style.height = '24px'
spinner.style.animation = 'rotate 1s linear infinite'

// 创建文本元素
const textDiv = document.createElement('div')
textDiv.textContent = '加载中...'
textDiv.style.marginLeft = '16px'
textDiv.style.color = '#2c3e50'

// 组合元素
this.loadingDiv.appendChild(spinner)
this.loadingDiv.appendChild(textDiv)

// 将加载层插入到目标元素中
el.appendChild(this.loadingDiv)

},

// 显示加载层

showLoading(el, binding) {

if (this.loadingDiv) {

// 动态更新配置

const { color, text, size } = binding.modifiers

if (color) this.loadingDiv.querySelector('.v-loading-spinner').style.borderColor = ${color} transparent

if (text) this.loadingDiv.querySelector('.v-loading-text').textContent = text

if (size) {

this.loadingDiv.querySelector('.v-loading-spinner').style.width = ${size}px

this.loadingDiv.querySelector('.v-loading-spinner').style.height = ${size}px

}

复制代码
  this.loadingDiv.style.display = 'flex'
}

},

// 隐藏加载层

hideLoading(el) {

if (this.loadingDiv) {

this.loadingDiv.style.display = 'none'

}

},

// 销毁加载层

destroy(el) {

if (this.loadingDiv) {

el.removeChild(this.loadingDiv)

this.loadingDiv = null

}

}

}

全局注册(main.js):

import Vue from 'vue'

import LoadingDirective from './directives/loading'

Vue.directive('loading', LoadingDirective)

Vue3 实现

// src/directives/loading.js

import { createVNode, render } from 'vue'

export default {

// 指令绑定到元素时触发

mounted(el, binding) {

this.init(el, binding)

},

// 指令值更新时触发

updated(el, binding) {

this.update(el, binding)

},

// 指令卸载时触发

unmounted(el) {

this.destroy(el)

},

// 初始化加载层

init(el, binding) {

// 创建加载层虚拟节点

this.loadingVNode = createVNode('div', {

class: 'v-loading-container',

style: {

position: 'absolute',

inset: '0',

backgroundColor: 'rgba(255,255,255,0.8)',

display: 'none',

justifyContent: 'center',

alignItems: 'center',

pointerEvents: 'none'

}

}, [

createVNode('div', {

class: 'v-loading-spinner',

style: {

border: '4px solid #2c3e50',

borderTopColor: 'transparent',

borderRadius: '50%',

width: '24px',

height: '24px',

animation: 'rotate 1s linear infinite'

}

}),

createVNode('div', {

class: 'v-loading-text',

style: { marginLeft: '16px', color: '#2c3e50' }

}, '加载中...')

])

复制代码
// 将加载层插入到目标元素中
render(this.loadingVNode, el)

},

// 更新加载状态

update(el, binding) {

if (binding.value) {

this.showLoading(el, binding)

} else {

this.hideLoading(el)

}

},

// 显示加载层

showLoading(el, binding) {

if (this.loadingVNode) {

// 动态更新配置

const { color, text, size } = binding.modifiers

if (color) {

this.loadingVNode.children0.props.style.borderColor = ${color} transparent

}

if (text) {

this.loadingVNode.children1.children = text

}

if (size) {

this.loadingVNode.children0.props.style.width = ${size}px

this.loadingVNode.children0.props.style.height = ${size}px

}

复制代码
  this.loadingVNode.props.style.display = 'flex'
  render(this.loadingVNode, el)
}

},

// 隐藏加载层

hideLoading(el) {

if (this.loadingVNode) {

this.loadingVNode.props.style.display = 'none'

render(this.loadingVNode, el)

}

},

// 销毁加载层

destroy(el) {

if (this.loadingVNode) {

render(null, el)

this.loadingVNode = null

}

}

}

全局注册(main.js):

import { createApp } from 'vue'

import LoadingDirective from './directives/loading'

createApp(App).directive('loading', LoadingDirective).mount('#app')

关键差异说明

特性 Vue2 实现 Vue3 实现

生命周期钩子 bind/inserted/update/unbind mounted/updated/unmounted

DOM 操作 直接创建原生 DOM 元素 使用 createVNode 和 render 函数操作虚拟 DOM

响应式更新 通过 update 钩子检测值变化 通过 updated 钩子自动响应 binding.value 变化

配置传递 使用 binding.modifiers 传递参数(如 v-loading.color) 支持 binding.arg 和 binding.modifiers 混合使用

内存管理 手动移除 DOM 元素 通过 render(null, el) 自动清理

动画实现 直接操作 style.animation 推荐使用 CSS 类名或 @keyframes 定义动画

使用示例

<button

v-loading="isLoading"

v-loading.color="#42b983"

v-loading.text="提交中..."

v-loading.size="32"

@click="handleSubmit"

复制代码
提交

根据项目使用的 Vue 版本选择对应的实现方式即可。Vue3 版本更推荐使用虚拟 DOM 操作,而 Vue2 版本则直接操作原生 DOM。

相关推荐
lichenyang45316 小时前
Docker 学习笔记(一):为什么需要镜像、容器和仓库?
前端
kyriewen16 小时前
别再对着 TypeScript 报错发呆了:我把 10 个最常见的红色波浪线翻译成了人话
前端·javascript·typescript
IT_陈寒16 小时前
SpringBoot自动配置的坑,我的API突然就404了
前端·人工智能·后端
暴走的小呆17 小时前
Vue 2 中 Object 的变化侦测:从 getter/setter 到 Dep、Watcher、Observer
vue.js
奇奇怪怪的17 小时前
Embedding 模型 10+ 横向评测
前端
陈广亮17 小时前
Monorepo 从 0 到 1 实操指南 2026 版:pnpm catalogs + Turborepo 2.x + changesets 全链路
前端
子兮曰17 小时前
OpenMontage 深度解剖:你的 AI 编程助手,其实是个视频工作室
前端·后端·ai编程
敲代码的鱼17 小时前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·前端·ios
英勇无比的消炎药18 小时前
TinyVue v-auto-tip: 文本超长自动提示的优雅方案
vue.js