使用Vue自定义指令实现组件遮罩层Loading效果
在Web开发中,加载状态(loading状态)是常见的用户交互反馈之一。当页面或组件的数据正在加载时,显示一个遮罩层可以阻止用户与当前界面进行交互,并提示用户数据正在加载中。Vue.js作为一种高效、灵活的JavaScript框架,提供了一种机制让我们能够轻松地实现这种效果,那就是自定义指令。
在本文中,我们将通过创建一个Vue自定义指令来实现组件级别的遮罩层loading效果。我们将分步骤介绍如何实现这个功能,并解释其中的关键概念。
步骤1:创建遮罩层组件
首先,我们需要创建一个遮罩层组件(MaskLayer.vue),该组件将用于在加载时覆盖在目标组件之上。
<!-- MaskLayer.vue --> <template> <div v-show="visible" class="mask-layer"> <div class="loading-spinner"> <!-- 这里可以放置任何你想要的加载动画 --> <span class="spinner"></span> </div> </div> </template> <script> export default { props: { visible: { type: Boolean, default: false } } }; </script> <style scoped> .mask-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.7); display: flex; align-items: center; justify-content: center; z-index: 9999; } .loading-spinner { /* 自定义加载动画样式 */ } </style>
步骤2:注册自定义指令
接下来,我们需要在Vue实例中注册一个自定义指令,用于控制遮罩层的显示和隐藏。
// main.js 或其他Vue实例初始化文件 import Vue from 'vue'; import MaskLayer from './components/MaskLayer.vue'; Vue.directive('loading', { // 当被绑定的元素插入到 DOM 中时…… inserted: function (el, binding) { // 创建遮罩层组件实例并挂载到DOM const maskLayer = new Vue({ render: (h) => h(MaskLayer, { props: { visible: false // 初始状态设置为隐藏 } }) }).$mount(); // 将遮罩层添加到目标元素的父级,并设置样式以确保它覆盖目标元素 el.parentNode.insertBefore(maskLayer.$el, el); // 保存遮罩层实例以便后续操作 el._maskLayer = maskLayer; // 根据binding.value的变化控制遮罩层的显示与隐藏 el._maskLayerVisible = binding.value; // 监听binding的变化 el._maskLayerBinding = binding; binding.instance.$watch(binding.expression, function(newVal, oldVal) { if (newVal !== oldVal) { el._maskLayer.visible = newVal; } }); }, // 当指令从元素上解绑时 unbind: function (el) { // 移除遮罩层 if (el._maskLayer && el._maskLayer.$el && el._maskLayer.$el.parentNode) { el._maskLayer.$el.parentNode.removeChild(el._maskLayer.$el); } } });
在上面的代码中,我们注册了一个名为loading
的自定义指令,该指令在插入元素到DOM时创建并插入遮罩层组件,并根据指令绑定的值控制遮罩层的显示与隐藏。
然而,上述实现存在一个问题,即inserted
钩子只会在元素初次插入DOM时被调用一次,而我们希望在绑定的值变化时也能更新遮罩层的显示状态。因此,我们需要稍微修改一下实现方式。
步骤3:改进自定义指令
为了响应绑定的值的变化,我们应该使用componentUpdated
钩子而不是inserted
钩子,并且我们需要处理遮罩层的显示逻辑,确保它正确地跟随绑定的值变化。
我们将采用动态组件和v-if
来控制遮罩层的显示和隐藏,而不是直接操作DOM。
首先,修改遮罩层组件,使其能够接受动态属性来控制显示状态:
<!-- MaskLayer.vue --> <template> <div v-if="show" class="mask-layer"> <!-- 加载动画 --> </div> </template> <script> export default { props: { show: { type: Boolean, default: false } } }; </script> <!-- 样式代码略 -->
然后,修改自定义指令的实现:
// main.js 或其他Vue实例初始化文件 import Vue from 'vue'; import MaskLayer from './components/MaskLayer.vue'; Vue.directive('loading', { // 当绑定元素插入到 DOM 中。 inserted: function (el, binding) { // 插入遮罩层组件 const maskLayerComponent = new Vue({ render(h) { return h(MaskLayer, { props: { show: false } }); } }).$mount(); // 插入遮罩层DOM元素 el.parentNode.insertBefore(maskLayerComponent.$el, el); // 在el上设置一个属性来存储遮罩层实例 el._maskLayer = maskLayerComponent; // 初始设置遮罩层显示状态 update(el, binding); }, // 组件更新 componentUpdated: function (el, binding) { // 更新遮罩层显示状态 update(el, binding); }, // 当指令从元素上解绑时 unbind: function (el) { // 移除遮罩层 if (el._maskLayer && el._maskLayer.$el && el._maskLayer.$el.parentNode) { el._maskLayer.$el.parentNode.removeChild(el._maskLayer.$el); } } }); // 更新遮罩层显示状态的函数 function update(el, binding) { if (binding.value) { el._maskLayer.show = true; } else { el._maskLayer.show = false; } }
现在,我们的自定义指令v-loading
已经能够在绑定的值变化时动态地显示和隐藏遮罩层了。
步骤4:在组件中使用自定义指令
最后,我们可以在任何组件或元素上使用v-loading
指令,并通过绑定一个布尔值来控制遮罩层的显示和隐藏。
<template> <div> <!-- 其他内容 --> <!-- 调用自定义指令 --> <button @click="toggleLoading">加载数据</button> <div v-loading="isLoading"> <!-- 这里是需要被遮罩层覆盖的内容 --> </div> </div> </template> <script> export default { data() { return { isLoading: false }; }, methods: { toggleLoading() { this.isLoading = !this.isLoading; // 模拟异步加载 setTimeout(() => { this.isLoading = false; }, 2000); } } }; </script>
在上面的示例中,当用户点击按钮时,isLoading
状态会在true
和false
之间切换,从而控制遮罩层的显示和隐藏。
这样,我们就成功使用Vue自定义指令实现了一个组件级别的遮罩层loading效果。这种方法的好处是它是可复用的,并且可以与任何组件或元素无缝集成,提供了一种灵活且可维护的方式来增强用户体验。
简单案例
时间有限,不上图了。
<div class="w-box-connent" v-whr-loading="isloading"></div>
大概意思就是,有一个div,当isloading==true时候我要给这个div上面蒙上一层遮罩层。
Vue.directive('whr-loading', { bind(el, bindingvnode, vnode) { let divzhezhao = document.createElement('div') divzhezhao.setAttribute('class', 'zhezhao') let divbaifenbai = document.createElement('div') divbaifenbai.setAttribute('class', 'baifenbai') let i = document.createElement('i') i.setAttribute('class', 'el-icon-loading') divzhezhao.append(divbaifenbai, i) el.insertBefore(divzhezhao, el.children[0]) divzhezhao.setAttribute('style', 'position: absolute;\n' + ' width: 100%;\n' + ' height: 100%;\n' + ' background-color: #0f7188b8;\n' + ' text-align: center;\n' + ' color:#ffffff96;\n' + ' font-size: 12px;') divbaifenbai.setAttribute('style','height: 100%;\n' + ' width: 0px;\n' + ' display: inline-block;\n' + ' vertical-align: middle;') if (bindingvnode.value == false){ divzhezhao.style.display = 'none' }else { divzhezhao.style.display = 'inline' } }, update(el, binding, vnode){ if (binding.value == false){ el.children[0].style.display = 'none' }else { el.children[0].style.display = 'inline' } } })
这里的el-icon-loading就是一个小圈在转那个样式。
快去试试吧!
注意这是2.0写法,3.0的同学改一下函数名称就好了。
5. 优化与扩展
5.1 全局遮罩层与局部遮罩层
上面的实现中,遮罩层是相对于被绑定元素局部显示的。如果需要全局遮罩层,则可以将遮罩层直接添加到document.body
上,而不是el.parentNode
。
5.2 自定义样式和类名
可以通过指令的binding.arg
或binding.modifiers
来传递额外的参数,如自定义样式或类名。
5.3 指令的双向绑定
虽然Vue指令不支持双向绑定,但你可以通过自定义事件和v-model
来模拟这一行为,从而更灵活地控制遮罩层。
5.4 遮罩层下的滚动行为
当遮罩层显示时,可能需要禁止页面滚动。这可以通过在遮罩层显示时给body
添加overflow: hidden
样式,并在遮罩层隐藏时移除该样式来实现。
5.5 动画和过渡效果
可以通过Vue的<transition>
组件来增强遮罩层的显示和隐藏动画。
5.6 指令的兼容性
确保你的自定义指令在所有目标浏览器上都能正常工作。特别是使用了较新的JavaScript特性时,需要考虑转译或提供polyfill。