vue directives自定义指令的使用
有的情况下,需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
自定义指令使用情景:
1.按钮级别权限的控制。
2.按钮的波纹动态效果。
3.一键copy的功能。
4.输入框自动聚焦。
5.下拉菜单,点击下拉菜单以外的地方时隐藏下拉菜单。
6.时间转换,比如朋友圈发布动态后的相对时间,比如刚刚、两分钟前等等。
7.输入框的最小值和最大值限制。
一:自定义指令有全局注册指令和局部注册指令两种方式:
全局注册指令:
Vue.directive('focus',{bind:function(){},inserted:function(){},update:function(){},componentUpdated:function(){},unbind:function(){}});
局部注册指令:在.vue文件中使用directives属性:
directives:{focus:{bind:function(){},inserted:function(){},update:function(){},componentUpdated:function(){},unbind:function(){}}}
注册指令成功后,直接在dom元素上使用v-focus。
<input v-focus/>
二:注册指令的使用。
下面以局部注册指令来举例子。
自定义指令有4个钩子函数,v-check-num="{key:'myNum',maxval:1000,minval:100}"
钩子函数需要用到的参数解析:
1.el:指令所绑定的元素。
2.binding:绑定对象。属性包含
name(指令名),不包括v-,此例子中为check-num。
value(计算后的指令所绑定的值),此例子中为{key:'myNum',maxval:1000,minval:100}。
oldValue(指令所绑定的前一个值,仅在update和componentUpdated中可用)。
express(绑定的值的字符串形式),此例子中为'{key:'myNum',maxval:1000,minval:100}'。
arg(传递给指令的参数),v-check-num:a中的arg为a。
modifiers(修饰符对象),v-my-directive.foo.bar中的modifiers为{foo:true,bar:true}。
3.vnode:编译生成的虚拟节点。属性有context为虚拟节点的上下文。
注意:el可读可写,其他参数只读。如果需要在钩子函数之间共享数据,可通过dataset来实现。
4.oldVnode:上一个虚拟节点。
directiveCom.vue:
<template><div><p>checkNum指令</p><label>数量:</label><input :value="myNum" v-check-num="{key:'myNum',maxval:1000,minval:100}" v-if="show" /><button @click="toggle">切换一下show</button></div>
</template><script>
export default {name: "directiveCom",data: () => {return {show: true,myNum: "" //奖品总数};},methods: {toggle() {this.show = !this.show;}},directives: {checkNum: {//只调用一次,第一次绑定指令到元素上时调用,可在此生命周期内做一些初始化的操作bind: function() {console.log("bind");},//被绑定元素插入父节点时调用inserted: function() {console.log("inserted");},//被绑定元素所在的模板被更新时即可调用update: function() {console.log("update");},//被绑定元素所在的模板完成一次更新周期时调用componentUpdated: function() {console.log("componentUpdated");},//指令与元素解绑的时候调用unbind: function() {console.log("unbind");}}}
};
</script> <style>
</style>
在App.vue里面引用上面的指令组件:
<template><div id="app"><directive-com/></div>
</template><script>import directiveCom from './components/directiveCom'
export default {name: "App",components:{directiveCom}
};
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>
效果图:
页面渲染时,触发了bind和inserted函数。
点击按钮,效果图:
此时,改变了directiveCom组件里的dom元素的css样式,触发了update和componentUpdated函数。
将directiveCom中的v-show改为v-if:
<template><div><p>checkNum指令</p><label>数量:</label><input :value="myNum" v-check-num="{key:'myNum',maxval:1000,minval:100}" v-if="show" /><button @click="toggle">切换一下show</button></div>
</template><script>
export default {name: "directiveCom",data: () => {return {show: true,myNum: "" //奖品总数};},methods: {toggle() {this.show = !this.show;}},directives: {checkNum: {//只调用一次,第一次绑定指令到元素上时调用,可在此生命周期内做一些初始化的操作bind: function() {console.log("bind");},//被绑定元素插入父节点时调用inserted: function() {console.log("inserted");},//被绑定元素所在的模板被更新时即可调用update: function() {console.log("update");},//被绑定元素所在的模板完成一次更新周期时调用componentUpdated: function() {console.log("componentUpdated");},//指令与元素解绑的时候调用unbind: function() {console.log("upbind");}}}
};
</script> <style>
</style>
点击按钮,效果如下:
v-if是dom组件的的销毁和创建,指令与元素解绑,此时触发了unbind函数。
三:进阶:使用自定义指令来设置输入框的最小值和最大值规则。规则:如果输入的值<最小值,那么默认为最小值;如果输入的值>最大值,那么默认为最大值;如果输入的是非数字,则清空输入框,默认为空。
directiveCom组件:
<template>
<div><p>checkNum指令</p><label>数量:</label><input :value="myNum" v-check-num="{key:'myNum',maxval:1000,minval:100}" v-if="show" /><button @click="toggle">切换一下show</button></div>
</template><script>
export default {name: "directiveCom",data: () => {return {show: true,myNum: "" //奖品总数};},methods: {toggle() {this.show = !this.show;}},directives: {checkNum: {//只调用一次,第一次绑定指令到元素上时调用,可在此生命周期内做一些初始化的操作bind: function(el, binding, vnode) {el.handler = function() {if (!Number(el.value)) {el.value = "";return false;}const value = Number(el.value);if (binding.value.maxval && value > binding.value.maxval) {el.value = binding.value.maxval;}if (binding.value.minval && value < binding.value.minval) {el.value = binding.value.minval;}//将el.val的值赋值给myNumvnode["context"][binding.value.key] = el.value;};el.addEventListener("change", el.handler);console.log("bind");},//被绑定元素插入父节点时调用inserted: function() {console.log("inserted");},//被绑定元素所在的模板被更新时即可调用update: function() {console.log("update");},//被绑定元素所在的模板完成一次更新周期时调用componentUpdated: function() {console.log("componentUpdated");},//指令与元素解绑的时候调用unbind: function() {console.log("unbind");}}}
};
</script> <style>
</style>
四:用自定义指令来实现几种需求场景:
git链接:https://github.com/xiaoli0510/vue-directive
directive.vue:
<template><div><div><p>1.validBtn指令:按钮级别权限的控制</p><button v-valid-btn="'viewBtn'">查看按钮</button><br /><!-- 通过ajax获取的用户按钮权限数组里面没有editBtn,所以编辑按钮被移除 --><button v-valid-btn="'editBtn'">编辑按钮</button></div><div><p>2.waves指令:按钮的波纹动态效果</p><button v-waves>点击有波纹效果的按钮</button></div><div><p>3.copy指令:一键copy的功能</p><button v-copy="'copyText'">点击一键复制</button></div><div><p>4.focus指令:输入框自动聚焦</p><input type="text" v-focus /></div><div><p>5.clickoutside指令:下拉菜单,点击下拉菜单以外的地方时隐藏下拉菜单</p><div class="main" v-clickoutside="handleClose"><button @click="showDrop =!showDrop">点击显示下拉菜单</button><div class="dropdown" v-show="showDrop"><p>我是下拉框的内容,点击外部区域可以关闭</p></div></div></div><div><p>7.time指令:时间转换,比如朋友圈发布动态后的相对时间,比如刚刚、两分钟前等等</p><div v-time="timeNow"></div></div><div><p>8.checkNum指令:输入框的最小值和最大值限制</p><label>数量:</label><input :value="myNum" v-check-num="{key:'myNum',maxval:1000,minval:100}" /></div></div>
</template><script>
export default {name: "directiveCom",data: () => {return {showDrop: false,myNum: "",timeNow: new Date().getTime()};},methods: {handleClose() {this.showDrop = false;}},directives: {//输入框的最小值和最大值限制checkNum: {//指令第一次绑定在元素上时调用,只调用一次bind: function(el, binding, vnode) {el._handler_ = function() {let value = el.value;if (!Number(value)) {el.value = "";return false;}value = Number(value);if (binding.value.maxval && value > binding.value.maxval) {el.value = binding.value.maxval;}if (binding.value.minval && value < binding.value.minval) {el.value = binding.value.minval;}vnode["context"][binding.value.key] = el.value;};el.addEventListener("change", el._handler_);},//被绑定的元素插入父节点时调用inserted: function() {},//被绑定的元素所在的模板发送更新时,比如style或者内容改变的时候调用update: function() {},//所在的模板完成一次更新后调用componentUpdated: function() {},//指令与元素解绑的时候调用unbind: function(el) {delete el._handler_;}},//按钮级别权限的控制validBtn: {inserted: function(el, binding) {el.handler = function(value) {//此处模拟的是ajax获取的用户权限按钮const btnArr = ["viewBtn"];for (var item of btnArr) {if (item === value) {return true;}}//如果用户没有此按钮权限,则移除此按钮el.parentNode.removeChild(el);return false;};el.handler(binding.value);},unbind: function(el) {delete el.handler;}},focus: {inserted: function(el) {el.focus();}},clickoutside: {bind: function(el, binding) {function documentHandler(e) {//如果点击的是当前的下拉框元素 则不做处理if (el.contains(e.target)) {return false;}//如果v-clickoutside后面有表达式 则执行后面的函数 此例子中执行handleClose函数if (binding.expression) {binding.value(e);}}el.__vueClickOutside__ = documentHandler;document.addEventListener("click", documentHandler);},unbind: function(el) {document.removeEventListener("click", el.__vueClickOutside__);delete el.__vueClickOutside__;}}}
};
</script> <style>
button {margin-bottom: 10px;
}
</style>
先上效果图:
checkNum、validBtn、focus、clickoutside是局部注册指令。
waves、copy、time是全局注册指定。
time.js:
var Time = {//获取当前时间戳getUnix: function () {var date = new Date();return date.getTime();},//获取今天0点0分0秒的时间戳getTodayUnix: function () {var date = new Date();date.setHours(0);date.setMinutes(0);date.setSeconds(0);date.setMilliseconds(0);return date.getTime();},//获取今年1月1日0点0分0秒的时间戳getYearUnix: function () {var date = new Date();date.setMonth(0);date.setDate(1);date.setHours(0);date.setMinutes(0);date.setSeconds(0);date.setMilliseconds(0);return date.getTime();},//获取标准年月日getLastDate: function (time) {var date = new Date(time);var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();return date.getFullYear() + '-' + month + '-' + day;},//转换时间getFormatTime: function (timestamp) {var now = this.getUnix(); //当前时间戳var today = this.getTodayUnix(); //今天0点时间戳//var year = this.getYearUnix(); //今年0点时间戳var timer = (now - timestamp) / 1000; //转换为妙级别时间戳var tip = '';if (timer <= 0) {tip = '刚刚';} else if (Math.floor(timer / 60) <= 0) {tip = '刚刚';} else if (timer < 3600) {tip = Math.floor(timer / 60) + '分钟前';} else if (timer >= 3600 && (timestamp - today >= 0)) {tip = Math.floor(timer / 3600) + '小时前';} else if (timer / 86400 <= 31) {tip = Math.ceil(timer / 86400) + '天前';} else {tip = this.getLastDate(timestamp);}return tip;}
};export default {bind: function (el, binding) {el.innerHTML = Time.getFormatTime(binding.value);el._timeout_ = setInterval(function () {el.innerHTML = Time.getFormatTime(binding.value);}, 1000);},unbind:function(el){clearInterval(el._timeout_);delete el._timeout_;}
}
copy.js:
let listenActionexport default {inserted(el, binding) {const params = binding.value || {}const stickyTop = params.stickyTop || 0const zIndex = params.zIndex || 1000const elStyle = el.styleelStyle.position = '-webkit-sticky'elStyle.position = 'sticky'// if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)// if (~elStyle.position.indexOf('sticky')) {// elStyle.top = `${stickyTop}px`;// elStyle.zIndex = zIndex;// return// }const elHeight = el.getBoundingClientRect().heightconst elWidth = el.getBoundingClientRect().widthelStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`const parentElm = el.parentNode || document.documentElementconst placeholder = document.createElement('div')placeholder.style.display = 'none'placeholder.style.width = `${elWidth}px`placeholder.style.height = `${elHeight}px`parentElm.insertBefore(placeholder, el)let active = falseconst getScroll = (target, top) => {const prop = top ? 'pageYOffset' : 'pageXOffset'const method = top ? 'scrollTop' : 'scrollLeft'let ret = target[prop]if (typeof ret !== 'number') {ret = window.document.documentElement[method]}return ret}const sticky = () => {if (active) {return}if (!elStyle.height) {elStyle.height = `${el.offsetHeight}px`}elStyle.position = 'fixed'elStyle.width = `${elWidth}px`placeholder.style.display = 'inline-block'active = true}const reset = () => {if (!active) {return}elStyle.position = ''placeholder.style.display = 'none'active = false}const check = () => {const scrollTop = getScroll(window, true)const offsetTop = el.getBoundingClientRect().topif (offsetTop < stickyTop) {sticky()} else {if (scrollTop < elHeight + stickyTop) {reset()}}}listenAction = () => {check()}window.addEventListener('scroll', listenAction)},unbind() {window.removeEventListener('scroll', listenAction)}
}
waves.js:
import './waves.css'const context = '@@wavesContext'function handleClick(el, binding) {function handle(e) {const customOpts = Object.assign({}, binding.value)const opts = Object.assign({ele: el, // 波纹作用元素type: 'hit', // hit 点击位置扩散 center中心点扩展color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色},customOpts)const target = opts.eleif (target) {target.style.position = 'relative'target.style.overflow = 'hidden'const rect = target.getBoundingClientRect()let ripple = target.querySelector('.waves-ripple')if (!ripple) {ripple = document.createElement('span')ripple.className = 'waves-ripple'ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'target.appendChild(ripple)} else {ripple.className = 'waves-ripple'}switch (opts.type) {case 'center':ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'breakdefault:ripple.style.top =(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||document.body.scrollTop) + 'px'ripple.style.left =(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||document.body.scrollLeft) + 'px'}ripple.style.backgroundColor = opts.colorripple.className = 'waves-ripple z-active'return false}}if (!el[context]) {el[context] = {removeHandle: handle}} else {el[context].removeHandle = handle}return handle
}export default {bind(el, binding) {el.addEventListener('click', handleClick(el, binding), false)},update(el, binding) {el.removeEventListener('click', el[context].removeHandle, false)el.addEventListener('click', handleClick(el, binding), false)},unbind(el) {el.removeEventListener('click', el[context].removeHandle, false)el[context] = nulldelete el[context]}
}
waves.css:
.waves-ripple {position: absolute;border-radius: 100%;background-color: rgba(0, 0, 0, 0.15);background-clip: padding-box;pointer-events: none;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;-webkit-transform: scale(0);-ms-transform: scale(0);transform: scale(0);opacity: 1;
}.waves-ripple.z-active {opacity: 0;-webkit-transform: scale(2);-ms-transform: scale(2);transform: scale(2);-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;transition: opacity 1.2s ease-out, transform 0.6s ease-out;transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}
在main.js中引入js文件,再进行全局注册:
main.js:
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falseimport waves from './directive/waves/waves.js'
Vue.directive('waves', waves)import copy from './directive/copy.js'
Vue.directive('copy', copy)import time from './directive/time.js'
Vue.directive('time', time)new Vue({render: h => h(App),
}).$mount('#app')
vue directives自定义指令的使用相关推荐
- VUE:自定义指令(directives )选项的用法
自定义指令分为全局指令和局部指令.全局指令可在任意vue组件内生效,局部指令仅在注册了指令的组件内生效. 全局指令和局部指令写法几乎相同.全局指令是在main.js内使用Vue.directive注册 ...
- Vue.js自定义指令的用法与实例
市面上大多数关于Vue.js自定义指令的文章都在讲语法,很少讲实际的应用场景和用例,以致于即便明白了怎么写,也不知道怎么用.本文不讲语法,就讲自定义指令的用法. 自定义指令是用来操作DOM的.尽管Vu ...
- VUE使用自定义指令对普通 DOM 元素进行底层操作
使用vue的自定义指令来整合某个重复使用的底层操作 举个聚焦输入框的例子,如下: 当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作).事实上,只要你在打开这 ...
- Vue的自定义指令以及Vue自定义指令的应用场景
前言 一.Vue的自定义指令分为? vue中除了核心功能内置的指令外,也允许注册自定义指令.有的情况下,对普通DOM元素进行底层操作,这时候就会用到自定义指令. 自定义指令分为全局自定义指令和局部的自 ...
- VUE通过自定义指令,只允许输入大写英文以及数字
在写之前需要知道几个功能对象的作用: 首先输入框输入的情况有键盘输入和粘贴输入的情况 用到的组件有: input.onblur-onblur: 事件会在对象失去焦点时发生.Onblur 经常用于Jav ...
- js:Vue.js自定义指令实现scroll下滑滚动翻页
Vue.js自定义指令实现scroll下滑滚动翻页 核心代码 import Vue from 'vue'// v-scroll Vue.directive('scroll', {bind(el, bi ...
- vue 项目自定义指令实现防抖
自定义v-debounce指令实现click事件防抖 该指令绑定的方法暂不支持传递参数,如需要传参数可以参考另一篇博客,防抖组件的实现 工具uitls文件夹创建debounce.js文件 代码如下 i ...
- Vue API(directives) 自定义指令
前言:除了vue的内置指令以外,我们可以定义自定义指令.内置指令表相见:https://www.cnblogs.com/ilovexiaoming/p/6840383.html 我们定义一个最简单的 ...
- Vue.js 自定义指令
简介 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令.注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件.然而,有的情况下,你仍然需要对普通 D ...
最新文章
- 时序约束,STA的QA
- 由Linux内核bug引起SSH登录缓慢问题的排查与解决
- 慈溪微生活图标_日常生活中的图标
- 北邮OJ 89. 统计时间间隔
- Charles调试Https iOS
- Myeclipse创建第一个web项目
- 加速你的Python
- 使用 Akka 实现 Master 与 Worker 之间的通信
- phpcmsV9上传文件类型的设置
- Spring Cloud 微服务实战系列-Eureka注册中心(一)
- maven(二) maven项目构建ssh工程(父工程与子模块的拆分与聚合)
- wps怎么把当前页面设置为横向_办公软件操作技巧011:如何将word文档的部分页面改为横向...
- GitHub Windows桌面版 中文汉化
- 高并发之——并发测试工具ab
- JAVA :一张纸厚0.5mm //0.0005m,折叠多少次,厚度会超过珠穆朗玛峰?(8848.43m)
- 伽利略定位系统的历史
- socket文件传输
- C#应用程序与MATLAB联合编程
- 【C++】Clang-Format:代码自动格式化(看这一篇就够了)
- C#-- 控制台操作