当我们刷新页面或者是首次加载的时候, 如果后端数据请求比较慢的情况下; 页面是会出现白屏情况的
所以我们可以使用 v-loading 去优化一下, 增加用户的体验性
首先, 我们需要去定义一个 loading 的组件
其组件中需要向外暴露出一个 setTitle 的函数, 用于来动态的去改变 loading 加载文字
这个组件主要是让 loading 的效果在页面的正中间去显示
<template>
<div class="loading">
<div class="loading-content">
<img width="24" height="24" src="./loading.gif">
<p class="desc">{{title}}</p>
</div>
</div>
</template>
<script>
export default {
name: 'loadingCom',
data () {
return {
title: '正在载入...'
}
},
methods: {
setTitle (title) {
this.title = title
}
}
}
</script>
<style lang="scss" scoped>
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
.loading-content {
text-align: center;
.desc {
line-height: 20px;
font-size: $font-size-small;
color: $color-text-l;
}
}
}
</style>
vue 给我们提供了一个 api (directive), 可以让我们去开发自定义的指令
其 api 中需要传入两个参数 app.directive('指令名称', 指令配置项)
下面我们需要做的是, 创建一个 js 文件作为 v-loading 指令的配置项
思路分析:
1. 首先导入 loading 组件和 createApp 方法
2. 创建一个 vue 实例, 将 loading 组件作为实例的根组件
3. 定义 v-loading 的配置项, 其中包含两个钩子函数(mounted和updated)
4. 在 mounted 钩子函数执行的时候, 动态的创建一个 DOM 对象; 然后将 app 挂载到这个 DOM 对象上面
5. 当 app 执行了 mount 方法之后, 可以得到做了对应逻辑处理的 vue 实例; 我们命名为 instance
6. 是否需要在目标元素(el)上面显示 loading 组件取决于 binding.value 的值
7. 判断 binding.value 如果为真, 就需要在 el 中插入先前创建的 DOM 元素(可以在 instance 上的 $el 中获取)
8. 组件更新时, 会执行 updated 钩子函数; 如果说 binding.value 的值不和原来 binding.oldValue 的相同, 就需要动态的去调用插入元素函数和移除元素函数
v-loading 指令还可以做一些优化
1. 在创建的 DOM 元素被挂载的时候, 可以动态的判断 el 的 style 样式是否满足"子绝父相"的定位条件; 如果不满足需要动态的添加 g-relative 类
2. 在目标组件挂载和更新阶段, 可以动态的去修改 v-loading 指令的显示文字
directive 给我们提供了一个钩子函数 arg , 可以给指令传参数
import { createApp } from 'vue'
import loading from './loading.vue'
import { addClass, removeClass } from '@/assets/js/dom'
// 相对定位的类名
const relativeCls = 'g-relative'
// loading的配置项
const loadingDirective = {
// 指令挂载时
mounted (el, binding) {
const app = createApp(loading)
const instance = app.mount(document.createElement('div'))
el.instance = instance
// 动态添加参数
const title = binding.arg
if (typeof title !== 'undefined') {
instance.setTitle(title)
}
if (binding.value) {
// 动态添加loading元素
append(el)
}
},
// 组件更新时
updated (el, binding) {
const title = binding.arg
if (typeof title !== 'undefined') {
el.instance.setTitle(title)
}
if (binding.value !== binding.oldValue) {
binding.value ? append(el) : remove(el)
}
}
}
// 给目标元素添加loading元素
function append (el) {
// 获取目标元素的style样式
const style = getComputedStyle(el)
// 如果目标元素style样式中没有fixed, absolute或relative其中一个的话, 是需要动态添加的
// 因为loading效果需要在页面中居中对齐
if (['fixed', 'absolute', 'relative'].indexOf(style.position) === -1) {
addClass(el, relativeCls)
}
el.appendChild(el.instance.$el)
}
// 移除目标元素的loading元素
function remove (el) {
removeClass(el, relativeCls)
el.removeChild(el.instance.$el)
}
export default loadingDirective
// 把对DOM的操作函数都会封装到这一个js文件下面
// 给目标元素添加类名
export function addClass (el, className) {
if (!el.classList.contains(className)) {
el.classList.add(className)
}
}
// 移除目标元素类名
export function removeClass (el, className) {
// 这里就不用做判断, 如果移除一个不存在的类名也是不会报错的
el.classList.remove(className)
}
body, html {
line-height: 1;
font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif, 'Droid Sans Fallback';
user-select: none;
-webkit-tap-highlight-color: transparent;
background: $color-background;
color: $color-text;
}
// 需要添加relative样式的时候, 直接给对应的DOM元素动态添加class类
.g-relative {
position: relative;
}
最后需要在 mian.js 中去全局注册这一个 v-loading 指令
// v-loading自定义指令
import loadingDirective from '@/components/base/loading/directive'
createApp(App).use(store).use(router).directive('loading', loadingDirective).mount('#app')
在组件中使用一下
<template>
<div v-loading:[loadingText]="loading"></div>
</template>
<script>
export default {
name: 'recommendCom',
components: { slider, scroll },
data () {
return {
// loading组件的文字
loadingText: '正在加载中......'
}
},
computed: {
loading () {
// 动态的判断loading值的改变
}
}
}
</script>
使用的时候, 我们可以直接在需要显示 loading 效果的元素中插入 v-loading 指令
这样要比手动的去引入, 然后通过 v-if 判断显示或不显示要优雅的很多
且如果多个组件都需要使用 loading 的话, 我们可以直接插入 v-loading 指令; 这样也方便的多