实现全局自定义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。

相关推荐
沐言人生8 小时前
82.4k 星!把十几万行代码变成知识图谱,新人终于不用硬啃了
前端·后端·github
溪语流沙8 小时前
【Web全栈进阶】JWT无状态认证:签发、校验、刷新
前端·git·python·github
在繁华处8 小时前
2.1 上下文:决定 Agent 能力上限的关键
前端·人工智能·microsoft
ndsc_d8 小时前
2026年有哪些好用的AI UI设计工具?主流工具功能和适用场景对比
前端·人工智能·ui·ai·设计师·ai ui·ai ui工具
溪语流沙8 小时前
【每天一个CSS | Day18】会摆动的拟物怀表:黄铜、皮革与催眠摆
前端·css3·html5
Lvan的前端笔记9 小时前
docker:基于docker的前端部署方案
前端·docker·状态模式
程序人生8889 小时前
从“把隐私文件上传到别人云端“到本地可控:DocConverter Web 立项初心与架构选型
前端·图像处理·人工智能·opencv·架构·ocr·文心一言
开开心心就好9 小时前
视频里的图片怎么提取?双击一下就导出
java·前端·人工智能·spring·智能手机·intellij-idea·excel
爱喝水的小周9 小时前
实验十 Vue‑Router 王者荣耀战绩查询 APP 开发实践
前端·javascript·vue.js
yexianglunbai9 小时前
Vue 3 全面详解:从核心特性到工程化实践
前端·javascript·vue.js