https://segmentfault.com/a/1190000038475001
在 Vue,除了核心功能默认内置的指令 ( v-model 和 v-show ),Vue 也允许注册自定义指令。它的作用价值在于当开发人员在某些场景下需要对普通 DOM 元素进行操作。
Vue 自定义指令有全局注册和局部注册两种方式。先来看看注册全局指令的方式,通过 Vue.directive( id, [definition] ) 方式注册全局指令。然后在入口文件中进行 Vue.use() 调用。
批量注册指令,新建 directives/index.js 文件
importcopyfrom’./copy’importlongpressfrom’./longpress’//自定义指令constdirectives={copy,longpress,}exportdefault{install(Vue){Object.keys(directives).forEach((key)=>{Vue.directive(key,directives[key])})},}
在 main.js 引入并调用
importVuefrom’vue’importDirectivesfrom’./JS/directives’Vue.use(Directives)
指令定义函数提供了几个钩子函数(可选):
bind: 只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作。inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值。componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。unbind: 只调用一次, 指令与元素解绑时调用。
下面分享几个实用的 Vue 自定义指令
复制粘贴指令 v-copy长按指令 v-longpress输入框防抖指令 v-debounce禁止表情及特殊字符 v-emoji图片懒加载 v-LazyLoad权限校验指令 v-premission实现页面水印 v-waterMarker拖拽指令 v-draggablev-copy
需求:实现一键复制文本内容,用于鼠标右键粘贴。
思路:
使用:给 Dom 加上 v-copy 及复制的文本即可
<template><buttonv-copy=”copyText”>复制</button></template><script>exportdefault{data(){return{copyText:’acopydirectives’,}},}</script>v-longpress
需求:实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件
思路:
使用:给 Dom 加上 v-longpress 及回调函数即可
<template><buttonv-longpress=”longpress”>长按</button></template><script>exportdefault{methods:{longpress(){alert(‘长按指令生效’)}}}</script>v-debounce
思路:
定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。将时间绑定在 click 方法上。constdebounce={inserted:function(el,binding){lettimerel.addEventListener(‘keyup’,()=>{if(timer){clearTimeout(timer)}timer=setTimeout(()=>{binding.value()},1000)})},}exportdefaultdebounce
使用:给 Dom 加上 v-debounce 及回调函数即可
<template><buttonv-debounce=”debounceClick”>防抖</button></template><script>exportdefault{methods:{debounceClick(){console.log(‘只触发一次’)}}}</script>v-emoji
背景:开发中遇到的表单输入,往往会有对输入内容的限制,比如不能输入表情和特殊字符,只能输入数字或字母等。
我们常规方法是在每一个表单的 on-change 事件上做处理。
<template><inputtype=”text”v-model=”note”@change=”vaidateEmoji”/></template><script>exportdefault{methods:{vaidateEmoji(){ var reg =/[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()- /*{}[]]|s/gthis.note=this.note.replace(reg,”)},},}</script>
这样代码量比较大而且不好维护,所以我们需要自定义一个指令来解决这问题。
需求:根据正则表达式,设计自定义处理表单输入规则的指令,下面以禁止输入表情和特殊字符为例。
letfindEle=(parent,type)=>{returnparent.tagName.toLowerCase()===type?parent:parent.querySelector(type)}consttrigger=(el,type)=>{conste=document.createEvent(‘HTMLEvents’)e.initEvent(type,true,true)el.dispatchEvent(e)}constemoji={bind:function(el,binding,vnode){//正则规则可根据需求自定义 var regRule =/[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()- /*{}[]]|s/glet$inp=findEle(el,’input’)el.$inp=$inp$inp.handle=function(){letval=$inp.value$inp.value=val.replace(regRule,”)trigger($inp,’input’)}$inp.addEventListener(‘keyup’,$inp.handle)},unbind:function(el){el.$inp.removeEventListener(‘keyup’,el.$inp.handle)},}exportdefaultemoji
使用:将需要校验的输入框加上 v-emoji 即可
<template><inputtype=”text”v-model=”note”v-emoji/></template>v-LazyLoad
背景:在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。
需求:实现一个图片懒加载指令,只加载浏览器可见区域的图片。
思路:
图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的拿到所有的图片 Dom ,遍历每个图片判断当前图片是否到了可视区范围内如果到了就设置图片的 src 属性,否则显示默认图片
图片懒加载有两种方式可以实现,一是绑定 srcoll 事件进行监听,二是使用 IntersectionObserver 判断图片是否到了可视区域,但是有浏览器兼容性问题。
下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 节流的方法实现。
constLazyLoad={//install方法install(Vue,options){constdefaultSrc=options.defaultVue.directive(‘lazy’,{bind(el,binding){LazyLoad.init(el,binding.value,defaultSrc)},inserted(el){if(IntersectionObserver){LazyLoad.observe(el)}else{LazyLoad.listenerScroll(el)}},})},//初始化init(el,val,def){el.setAttribute(‘data-src’,val)el.setAttribute(‘src’,def)},//利用IntersectionObserver监听elobserve(el){vario=newIntersectionObserver((entries)=>{constrealSrc=el.dataset.srcif(entries[0].isIntersecting){if(realSrc){el.src=realSrcel.removeAttribute(‘data-src’)}}})io.observe(el)},//监听scroll事件listenerScroll(el){consthandler=LazyLoad.throttle(LazyLoad.load,300)LazyLoad.load(el)window.addEventListener(‘scroll’,()=>{handler(el)})},//加载真实图片load(el){constwindowHeight=document.documentElement.clientHeightconstelTop=el.getBoundingClientRect().topconstelBtm=el.getBoundingClientRect().bottomconstrealSrc=el.dataset.srcif(elTop-windowHeight<0&&elBtm>0){if(realSrc){el.src=realSrcel.removeAttribute(‘data-src’)}}},//节流throttle(fn,delay){lettimerletprevTimereturnfunction(…args){constcurrTime=Date.now()constcontext=thisif(!prevTime)prevTime=currTimeclearTimeout(timer)if(currTime-prevTime>delay){prevTime=currTimefn.apply(context,args)clearTimeout(timer)return}timer=setTimeout(function(){prevTime=Date.now()timer=nullfn.apply(context,args)},delay)}},}exportdefaultLazyLoad
使用,将组件内 标签的 src 换成 v-LazyLoad
<imgv-LazyLoad=”xxx.jpg”/>v-permission
背景:在一些后台管理系统,我们可能需要根据用户角色进行一些操作权限的判断,很多时候我们都是粗暴地给一个元素添加 v-if / v-show 来进行显示隐藏,但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以通过全局自定义指令来处理。
需求:自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。
思路:
自定义一个权限数组判断用户的权限是否在这个数组内,如果是则显示,否则则移除 DomfunctioncheckArray(key){letarr=[‘1′,’2′,’3′,’4’]letindex=arr.indexOf(key)if(index>-1){returntrue//有权限}else{returnfalse//无权限}}constpermission={inserted:function(el,binding){letpermission=binding.value//获取到v-permission的值if(permission){lethasPermission=checkArray(permission)if(!hasPermission){//没有权限移除Dom元素el.parentNode&&el.parentNode.removeChild(el)}}},}exportdefaultpermission
使用:给 v-permission 赋值判断即可
<divclass=”btns”><!–显示–><buttonv-permission=”‘1′”>权限按钮1</button><!–不显示–><buttonv-permission=”’10′”>权限按钮2</button></div>vue-waterMarker
需求:给整个页面添加背景水印
思路:
使用 `canvas` 特性生成 `base64` 格式的图片文件,设置其字体大小,颜色等。将其设置为背景图片,从而实现页面或组件水印效果functionaddWaterMarker(str,parentNode,font,textColor){//水印文字,父元素,字体,文字颜色varcan=document.createElement(‘canvas’)parentNode.appendChild(can)can.width=200can.height=150can.style.display=’none’varcans=can.getContext(‘2d’)cans.rotate((-20*Math.PI)/180)cans.font=font||’16pxMicrosoftJhengHei’cans.fillStyle=textColor||’rgba(180,180,180,0.3)’cans.textAlign=’left’cans.textBaseline=’Middle’cans.fillText(str,can.width/10,can.height/2)parentNode.style.backgroundImage=’url(‘ can.toDataURL(‘image/png’) ‘)’}constwaterMarker={bind:function(el,binding){addWaterMarker(binding.value.text,el,binding.value.font,binding.value.textColor)},}exportdefaultwaterMarker
使用,设置水印文案,颜色,字体大小即可
<template><divv-waterMarker=”{text:’lzg版权所有’,textColor:’rgba(180,180,180,0.4)’}”></div></template>
效果如图所示
v-draggable
需求:实现一个拖拽指令,可在页面可视区域任意拖拽元素。
思路:
设置需要拖拽的元素为相对定位,其父元素为绝对定位。鼠标按下`(onmousedown)`时记录目标元素当前的 `left` 和 `top` 值。鼠标移动`(onmousemove)`时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 `left` 和 `top` 值鼠标松开`(onmouseup)`时完成一次拖拽constdraggable={inserted:function(el){el.style.cursor=’move’el.onmousedown=function(e){letdisx=e.pageX-el.offsetLeftletdisy=e.pageY-el.offsetTopdocument.onmousemove=function(e){letx=e.pageX-disxlety=e.pageY-disyletmaxX=document.body.clientWidth-parseInt(window.getComputedStyle(el).width)letmaxY=document.body.clientHeight-parseInt(window.getComputedStyle(el).height)if(x<0){x=0}elseif(x>maxX){x=maxX}if(y<0){y=0}elseif(y>maxY){y=maxY}el.style.left=x ‘px’el.style.top=y ‘px’}document.onmouseup=function(){document.onmousemove=document.onmouseup=null}}},}exportdefaultdraggable
使用: 在 Dom 上加上 v-draggable 即可
<template><divclass=”el-dialog”v-draggable></div></template>
参考资料[1]
github.com/Michael-lzg…: https://github.com/Michael-lzg/my–article/blob/master/vue/分享8个Vue自定义指令.md
[2]
github.com/Michael-lzg…: https://github.com/Michael-lzg/v-directives
[3]
github.com/Michael-lzg…: https://github.com/Michael-lzg/v-directives
近期面试官:说说你对vue的mixin的理解,有哪些应用场景?漫画 | 人到中年,一地鸡毛