实现全局自定义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.children[0].props.style.borderColor = ${color} transparent

}

if (text) {

this.loadingVNode.children[1].children = text

}

if (size) {

this.loadingVNode.children[0].props.style.width = ${size}px

this.loadingVNode.children[0].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。

相关推荐
Data_Journal几秒前
Node.js网络爬取指南——简单易上手!
大数据·linux·服务器·前端·javascript
a11177611 分钟前
可视化角色权限配置页面(html 开源)
前端·开源·html
Lee川18 分钟前
个人中心与 AI 头像生成:从页面到 DALL-E 的完整实现
前端·架构
tedcloud1236 小时前
UI-TARS-desktop部署教程:构建AI桌面自动化系统
服务器·前端·人工智能·ui·自动化·github
UXbot9 小时前
AI原型设计工具如何支持团队协作与快速迭代
前端·交互·个人开发·ai编程·原型模式
ZC跨境爬虫10 小时前
跟着MDN学HTML_day_48:(Node接口)
前端·javascript·ui·html·音视频
PieroPc11 小时前
CAMWATCH — 局域网摄像头监控系统 Fastapi + html
前端·python·html·fastapi·监控
巴巴博一12 小时前
2026 最新:Trae / Cursor 一键接入 taste-skill 完整教程(让 AI 前端告别“AI 味”)
前端·ai·ai编程
kyriewen12 小时前
半夜三点线上崩了,AI替我背了锅——用AI排错,五分钟定位三年老bug
前端·javascript·ai编程
kyriewen13 小时前
我让 AI 当了 24 小时全年无休的“毒舌考官”
前端·ci/cd·ai编程