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

相关推荐
zhangminghuan15 分钟前
Vue vs React 实战拓展面试题|摒弃基础八股,聚焦业务开发核心难点(2026前端进阶)
前端
IT_陈寒31 分钟前
Redis主从切换竟让业务卡了3秒?这个坑我替你踩了
前端·人工智能·后端
天若有情67334 分钟前
独立开发复盘:我用 Node.js 做了个在线工具箱
前端·json·工具
xieliyu.38 分钟前
前端基础:常见CSS选择器用法
前端·css·笔记·vscode
zttbee1 小时前
前端测试内容
前端
2603_965896621 小时前
async/await|JS同步写法解决异步终极方案
前端·javascript
A24207349303 小时前
TCP 三次握手与四次挥手:从原理到状态机一次讲清
前端
GreenTea10 小时前
GrokBot 核心成员 Lauren Tan:每月交付 2000 个 PR 的人,是怎么用 AI 的
前端·后端·架构
mCell11 小时前
用 Knip 清理 AI Coding 留下的冗余代码
前端·ai编程·前端工程化
dy171712 小时前
vue2树节点刷新后定位到上一节点展开
vue.js