手写 vue 自定义指令(directive) v-loading 加载,项目实战

手写 vue 自定义指令(directive) v-loading 加载,项目实战

目标:

  1. 在vue项目的标签里面使用 v-loading="true" ,如同element-ui里面使用v-loading="true"一样 会展示loading 效果。
  2. 学会后可以直接复制到你的项目中实战,工作中vue项目肯定是会用到v-loading,一次引入全局使用,超级方便

在这里我做两个laoding效果:

  1. 全屏的loading效果,用于进入页面的时候使用loaidng
  2. 局部的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则插入到目标元素

案例使用教学:

按照上面的目录结构,把对应文件的代码复制进去,即可使用

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:

在这里插入图片描述
在这里插入图片描述

局部load:

在这里插入图片描述

在这里插入图片描述

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端酱紫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值