在写一个H5项目,然后需要一个全局使用的 loading , 用插件也可以搞定,但是项目周期不怎么紧张,所以决定自己写一个。
写一个全局的 componnets 也行,但是需要在每个使用的页面进行导入并且注册,因为 loading 用到的地方会很多,所以我觉得需要更优雅的来搞定它 !!!
所以就选择使用自定义指令来实现,写指令麻烦点,但是写好以后用起来香嘛,全局注册一次,就可以随便使用啦!!!
OK 接下来就开始操作起来!
先看看loading的效果这是最终的效果,期望在页面未加载出来的时候上下左右居中显示,我不会上传gif图片hahaha , 所以看起来它是静止的,其实他是转圈圈的。
我要写一个这样的组件,在写组件这里我就不多做赘述,很基础的东西,我把代码贴上来
<template>
<div class="container">
<img src="@/assets/img/loading.gif" alt="" />
<p>{{ title }}</p>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
const title = ref('')
const setTitle = val => {
title.value = val
}
return { title, setTitle }
}
}
</script>
<style lang="scss" scope>
.container {
width: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
img {
width: 2.133333rem;
}
p {
color: #fff;
}
}
</style>
我们就来新建一个js文件,用来写loading的自定义指令逻辑
const loadingDirective = {
mounted(el,binding) {
},
updated(el,binding) {
},
}
return loadingDirective // 导出
我们在创建的 loadingDirective 对象中,写入两个钩子函数,因为我们希望在这两个周期,对它进行操作,
那我们怎么创建这个组件对应的 DOM 呢?
我们先新建一个新的vue实例,再去进行一个动态的挂载,挂载之后我们就可以拿到这个 DOM 的实例了,具体怎么做呢我们再继续往下写
import { createApp } from 'vue' //导入createApp方法
import Loading from './loading' // 导入我们写好的 loading 组件
const loadingDirective = {
mounted(el, binding) {
// 创建app对象 跟组件为我们写好的 loading 组件
const app = createApp(Loading)
//动态创建一个div节点,将app挂载在div上,我们的 loading 组件将替换此 div 标签的 innerHTML
const instance = app.mount(document.createElement('div'))
},
updated(el, binding) {},
}
return loadingDirective // 导出
接下来我们要到了在 mounted 的时候,loading 是否显示的判断了,在做判断之前,先来看看,我们在写好loading自定义指令之后,该如何使用
<template>
<div v-loading="loading"></div> // 只需在节点上写上 v-loading='loading' 即可, 后边的loading是一个值
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup () {
const loading = ref(true) // 默认让loading 值为true ,传给loading组件
onMounted(() => {
setTimenout(() => { //模拟数据加载完成之后更改 loading 状态
loading.value = false
}, 5000)
})
return { loading}
}
}
</script>
此时我们在loading 组件中就可接收到传入的值,我们根据值来判断是否显示 loading 组件
const loadingDirective = {
mounted(el, binding) {
// 创建app对象 跟组件为我们写好的 loading 组件
const app = createApp(Loading)
//动态创建一个div节点,将app挂载在div上
const instance = app.mount(document.createElement('div'))
// 因为在updated也需要用到 instance 所以将 instance 添加在el上 ,在updated中通过el.instance 可访问到
el.instance = instance
if (binding.value) { // v-loading传过来的值储存在 binding.value 中
append(el)
}
},
updated(el, binding) {
remove(el)
},
}
function append(el) {
el.appendChild(el.instance.$el) //向el节点插入动态创建的 div 节点 , 内容就是我们的 loading 组件
}
function remove(el) {
el.removeChild(el.instance.$el) //移除动态创建的 div 节点
}
上边我写了两个方法,一个是插入节点,一个是移除节点,在一开始的时候将节点插入,在v-loading的值发生改变时候出发updated我们将节点移除, 但是这样写是不合适的,我们需要完善一下在updated中的操作,
updated (el, binding) {
if (binding.value !== binding.oldValue) { // 如果value的值有改变,那么我们去判断进行操作
binding.value ? append(el) : remove(el) // 三元表达式 true 添加,false 移除
}
}
因为我们需要在全局使用,那么我们将在 main.js 中去引入注册
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/scss/index.scss'
import loadingDirective from '@/views/loading/directive' //引入loading自定义指令
createApp(App)
.use(store)
.use(router)
.directive('loading', loadingDirective) //全局注册loading指令
.mount('#app')
现在一个简单的指令已经写完了,但是它还有两点需要完善的地方
1.是它位置定位的问题,如果所在挂载的 DOM 元素有定位属性,那么没有问题,但是所在挂载的 DOM 元素没有定位的时候,那么它自身的绝对定位,就有可能出现问题。
2.我们的需求还有一段文字需要进行展示,或许不同的地方需要等待时的文案会不同。
首先来解决第一个小问题 , 在插入的时候去判断挂载的节点有没有定位,如果没有则为其添加相对定位
const className = 'g-relative' // g-relative 是我在全局写的样式名
function append (el) {
const style = getComputedStyle(el)
if (['absolute', 'relative', 'fixed'].indexOf(style.position) === -1) {
el.classList.add(className) // 通过此API可以添加类名
}
el.appendChild(el.instance.$el) //向el节点插入动态创建的 div 节点 , 内容就是我们的 loading 组件
}
g-relative 是我在全局scss文件中写的样式
.g-relative {
position: relative;
}
在解决最后一个小问题,动态显示文案
在使用的地方使用 :[]的语法
<template>
<div class="container" v-loading:[title]="loading" ref="hello"></div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup () {
const title = ref('请稍后...')
const loading = ref(true)
return { loading, title }
}
}
</script>
传入title之后需要进行接收,并插入title在loading中
通过binding.arg可接收到:[]传来的值
const loadingDirective = {
mounted (el, binding) {
const app = createApp(loading)
const instance = app.mount(document.createElement('div'))
el.instance = instance
if (binding.value) {
append(el)
}
if (binding.arg !== 'undefined') { // 在此判断是否有title值
instance.setTitle(binding.arg) // setTitle 使我们在loading组件中定义的方法,可返回第一段代码查看
}
},
updated (el, binding) {
if (binding.arg !== 'undefined') { // 在此判断是否有title值
el.instance.setTitle(binding.arg) // // setTitle 使我们在loading组件中定义的方法,可返回第一段代码查看
}
if (binding.value !== binding.oldValue) {
binding.value ? append(el) : remove(el)
}
}
}
现在就完全实现了 loading 自定义指令的实现!
哪里有问题还请留言交流哦 886