手写 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则插入到目标元素


函数属性

  • 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:绑定前的值。仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
  • 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:

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

局部load:

在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自juejin.im/post/7017738225796005918