手写 vue 自定义指令(directive) v-loading 加载,项目实战
目标:
- 在vue项目的标签里面使用
v-loading="true"
,如同element-ui里面使用v-loading="true"一样 会展示loading 效果。- 学会后可以直接复制到你的项目中实战,工作中vue项目肯定是会用到v-loading,一次引入全局使用,超级方便
在这里我做两个laoding效果:
- 全屏的loading效果,用于进入页面的时候使用loaidng
- 局部的load 效果,用户分页或者加载部分内容的时候load效果
展示效果:
全屏加载:<div v-loading="true"></div>
局部加载:<div v-load="true"></div>
目录结构
> 多余的文件夹就不展示了
> loading里面的5个文件创建好,把代码复制进去,然后在main.js里面use一下就可以使用了
> 文章底部有使用案例,不会操作可以留言
|-- src
|-- App.vue
|-- main.js // 需要用到
|-- components
| |-- HelloWorld.vue
|-- directive // 需要用到
| |-- loading
| |-- index.js
| |-- load.js // 局部load
| |-- load.vue // 局部load
| |-- loading.js // 全局loading
| |-- loading.vue // 全局loading
复制代码
1. 创建文件
directive文件夹下创建loading文件夹,在创建:index.js、loading.js 、loading.vue 、load.js 、load.vue index.js文件用来暴露安装插件接口,这个下面会有说明。有了 Vue.directive这个方法就可以在页面中使用自定义指令 v-loading了
loading/index.js
import load from './load';
import loading from './loading';
export default {
install(Vue) {
Vue.directive("load", load), // 局部load
Vue.directive("loading", loading) // 全局loading
}
}
复制代码
loading/loading.vue
是一个组件,用来插入到自定义指令的目标元素中,这里可以写一些loading样式
// directive/loading/loading.vue
<template>
<div v-show="visible" class="loading-wrap">
<div class="loading-box">
<div class="loading-add"></div>
<div class="loading-txt">全局加载中...</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
};
},
};
</script>
<style scoped>
.loading-wrap {
position: absolute;
left: 0 !important;
right: 0 !important;
top: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: 100vh !important;
background: rgba(0, 0, 0, 0.7);
white-space: nowrap;
}
.loading-box {
user-select: none;
font-size: 16px;
white-space: nowrap;
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
justify-content: center;
}
.loading-add {
width: 16px;
height: 4px;
background-color: #eee;
display: inline-block;
margin-right: 4px;
position: relative;
animation: add 1s 0s linear infinite forwards;
border-radius: 4px;
}
.loading-add::after {
content: "";
display: inline-block;
width: 16px;
height: 4px;
background-color: #eee;
position: absolute;
left: 50%;
top: 50%;
border-radius: 4px;
transform: translate(-50%, -50%) rotateZ(90deg);
}
@keyframes add {
0% {
transform: rotateZ(0);
}
100% {
transform: rotateZ(360deg);
}
}
.loading-txt {
color: #eee;
animation: fontColor 3s 0s linear infinite reverse;
display: inline-block;
}
@keyframes fontColor {
0% {
color: rgba(238, 238, 238, 0.85);
}
25% {
color: rgba(135, 207, 235, 0.85);
}
50% {
color: rgba(255, 0, 0, 0.85);
}
75% {
color: rgba(51, 51, 51, 0.85);
}
100% {
color: rgba(255, 255, 0, 0.85);
}
}
</style>
复制代码
loading/loading.js
// directive/loading/loading.js
import Vue from 'vue'
import Loading from './loading.vue'
const Mask = Vue.extend(Loading)
const toggleLoading = (el, binding) => {
if (binding.value) {
Vue.nextTick(() => {
// 控制loading组件显示
el.instance.visible = true
// 插入到目标元素
insertDom(el, el, binding)
})
} else {
el.instance.visible = false
}
}
const insertDom = (parent, el) => {
parent.appendChild(el.mask)
}
export default {
bind: function (el, binding, vnode) {
const mask = new Mask({
el: document.createElement('div'),
data() { }
})
el.instance = mask
el.mask = mask.$el
el.maskStyle = {}
binding.value && toggleLoading(el, binding)
},
update: function (el, binding) {
if (binding.oldValue !== binding.value) {
toggleLoading(el, binding)
}
},
unbind: function (el, binding) {
el.instance && el.instance.$destroy()
}
}
复制代码
loading/load.js
// directive/loading/load.js
import Vue from 'vue';
import Load from './load.vue';
const Mask = Vue.extend(Load);
const toggleLoading = (el, binding) => {
if (binding.value) {
Vue.nextTick(() => {
el.instance.visible = true// 控制loading组件显示
insertDom(el, el, binding)// 插入到目标元素
})
} else {
el.instance.visible = false
}
}
const insertDom = (parent, el) => {
parent.appendChild(el.mask)
}
export default {
// bind(){}当绑定指令的时候出发
bind: function (el, binding, vnode) {
const mask = new Mask({
el: document.createElement('div'),
data() { }
})
el.instance = mask
el.mask = mask.$el
el.maskStyle = {}
binding.value && toggleLoading(el, binding)
},
// update(){}当数据更新时候会触发该函数
update: function (el, binding) {
if (binding.oldValue !== binding.value) {
toggleLoading(el, binding)
}
},
// unbind(){}解绑的时候触发该函数
unbind: function (el, binding) {
el.instance && el.instance.$destroy()
}
}
复制代码
loading/load.vue
// directive/loading/load.vue
<template>
<div v-show="visible" class="load-wrap">
<div class="loading-box">
<div class="loading-add"></div>
<div class="loading-txt">加载中...</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
};
},
};
</script>
<style scoped>
.load-wrap {
white-space: nowrap;
}
.loading-box {
user-select: none;
display: flex;
align-items: center;
justify-content: center;
}
.loading-add {
width: 16px;
height: 4px;
background-color: #eee;
display: inline-block;
margin-right: 4px;
position: relative;
animation: add 1s 0s linear infinite reverse;
border-radius: 4px;
}
.loading-add::after {
content: "";
display: inline-block;
width: 16px;
height: 4px;
background-color: #eee;
position: absolute;
left: 50%;
top: 50%;
border-radius: 4px;
transform: translate(-50%, -50%) rotateZ(90deg);
}
@keyframes add {
0% {
transform: rotateZ(0);
}
100% {
transform: rotateZ(-360deg);
}
}
.loading-txt {
color: #eee;
display: inline-block;
font-size: 16px;
animation: fontColor 3s 0s linear infinite reverse forwards;
}
@keyframes fontColor {
0% {
color: rgba(238, 238, 238, 0.85);
}
25% {
color: rgba(135, 207, 235, 0.85);
}
50% {
color: rgba(255, 0, 0, 0.85);
}
75% {
color: rgba(51, 51, 51, 0.85);
}
100% {
color: rgba(255, 255, 0, 0.85);
}
}
</style>
复制代码
- Vue.extend 接受参数并返回一个构造器,new 该构造器可以返回一个组件实例。
- 当我们 new Mask() 的时候,把该组件实例挂载到一个 div 上,但是这时候这个 div 还没有挂载到页面中。把它打印出来。
- 然后用一个变量接住mask实例 el.instance = mask
- 接下来判断 value 是否为 true ,如果是 true 则执行 toggleLoading ,toggleLoading 方法用来控制是否显示 loading.vue 组件中的 visible变量,并且如果 value是true则插入到目标元素
函数属性
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
- inserted:被绑定元素插入父节点时调用
- update:所在组件的 VNode 更新时调用,
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
- unbind:只调用一次,指令与元素解绑时调用。
函数参数
- el:指令所绑定的元素,可以用来直接操作 DOM。
- binding: 里面包含事件信息
- vnode:Vue 编译生成的虚拟节点。
- oldNode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
binding包含内容:
- name:指令名
- value:绑定的值 例如:
v-my-directive="1 + 1"
中,绑定值为 2。 - oldValue:绑定前的值。仅在
update
和componentUpdated
钩子中可用。无论值是否改变都可用。 - expression:字符串形式的指令表达式。例如
v-my-directive="1 + 1"
中,表达式为 "1 + 1"。 - arg 传给指令的参数,可选。例如
v-my-directive:foo
中,参数为 "foo"。 - modifiers:一个包含修饰符的对象。例如:
v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
案例使用教学:
按照上面的目录结构,把对应文件的代码复制进去,即可使用
src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 引入loading (里面包含了全局loading和局部load)
import loading from './directive/loading'
Vue.use(loading);
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
复制代码
src/components/HelloWorld.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<!-- 全局loading true-展示 false-隐藏 -->
<div v-loading="true"> </div>
<!-- 局部load true-展示 false-隐藏 -->
<div v-load="false"> </div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
};
</script>
<style scoped></style>
复制代码
全局loading: