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

相关推荐
武子康9 小时前
Search Console Platform Properties 扩大 SEO 资产边界:从 Page Ranking 到 Topic Coverage(5 类误读边界 + 3 表数据层设计)
前端·人工智能·后端
一点一木10 小时前
从60首歌到1个网站:输入你的故事,还你一首歌
前端·github
IT_陈寒11 小时前
Vue这个特性差点让我加班到凌晨,谁懂啊
前端·人工智能·后端
kyriewen11 小时前
我让AI改一个bug——它偷偷动了5个我没让它碰的地方
前端·javascript·ai编程
遇乐的果园12 小时前
前端学习笔记-vue加载渲染优化
前端·笔记·学习
用户9850335932813 小时前
OpenLayers 热力图从原理到 Vue 组件一键接入
前端
颜酱13 小时前
01 | 骨架搭建:FastAPI + Vue 跑通第一个 SSE 流式问答
前端·人工智能·后端
hoLzwEge13 小时前
解码 IDE 智能提示:jsconfig.json 辅助开发全攻略
前端·前端框架
leoZ23113 小时前
记忆系统与 Agent 定制完全指南(四):自定义 Agent 开发(一)
前端·chrome
慧一居士13 小时前
Element Plus 按需引入的配置使用说明和完整示例
前端·vue.js