分享8个非常实用的Vue自定义指令

在 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-draggable

v-copy

需求:实现一键复制文本内容,用于鼠标右键粘贴。

思路:

动态创建textarea标签,并设置readOnly属性及移出可视区域

将要复制的值赋给textarea标签的value属性,并插入到body

选中值textarea并复制

将body中插入的textarea移除

在第一次调用时绑定事件,在解绑时移除事件

constcopy = {bind(el, { value }){    el.$value = value    el.handler =() =>{if(!el.$value) {// 值为空的时候,给出提示。可根据项目UI仔细设计console.log('无复制内容')return}// 动态创建 textarea 标签consttextarea =document.createElement('textarea')// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域textarea.readOnly ='readonly'textarea.style.position ='absolute'textarea.style.left ='-9999px'// 将要 copy 的值赋给 textarea 标签的 value 属性textarea.value = el.$value// 将 textarea 插入到 body 中document.body.appendChild(textarea)// 选中值并复制textarea.select()constresult =document.execCommand('Copy')if(result) {console.log('复制成功')// 可根据项目UI仔细设计}document.body.removeChild(textarea)    }// 绑定点击事件,就是所谓的一键 copy 啦el.addEventListener('click', el.handler)  },// 当传进来的值更新的时候触发componentUpdated(el, { value }){    el.$value = value  },// 指令与元素解绑的时候,移除事件绑定unbind(el){    el.removeEventListener('click', el.handler)  },}exportdefaultcopy复制代码

使用:给 Dom 加上v-copy及复制的文本即可

复制exportdefault{data(){return{copyText:'a copy directives',      }    },  }复制代码

v-longpress

需求:实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件

思路:

创建一个计时器, 2 秒后执行函数

当用户按下按钮时触发mousedown事件,启动计时器;用户松开按钮时调用 mouseout事件。

如果mouseup事件 2 秒内被触发,就清除计时器,当作一个普通的点击事件

如果计时器没有在 2 秒内清除,则判定为一次长按,可以执行关联的函数。

在移动端要考虑touchstart,touchend事件

constlongpress = {bind:function(el, binding, vNode){if(typeofbinding.value !=='function') {throw'callback must be a function'}// 定义变量letpressTimer =null// 创建计时器( 2秒后执行函数 )letstart =(e) =>{if(e.type ==='click'&& e.button !==0) {return}if(pressTimer ===null) {        pressTimer =setTimeout(() =>{          handler()        },2000)      }    }// 取消计时器letcancel =(e) =>{if(pressTimer !==null) {clearTimeout(pressTimer)        pressTimer =null}    }// 运行函数consthandler =(e) =>{      binding.value(e)    }// 添加事件监听器el.addEventListener('mousedown', start)    el.addEventListener('touchstart', start)// 取消计时器el.addEventListener('click', cancel)    el.addEventListener('mouseout', cancel)    el.addEventListener('touchend', cancel)    el.addEventListener('touchcancel', cancel)  },// 当传进来的值更新的时候触发componentUpdated(el, { value }){    el.$value = value  },// 指令与元素解绑的时候,移除事件绑定unbind(el){    el.removeEventListener('click', el.handler)  },}exportdefaultlongpress复制代码

使用:给 Dom 加上v-longpress及回调函数即可

长按exportdefault{methods: {    longpress () {      alert('长按指令生效')    }  }}复制代码

v-debounce

背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如新增表单的提交按钮,多次点击就会新增多条重复的数据。

需求:防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。

思路:

定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。

将事件绑定在 click 方法上。

constdebounce = {inserted:function(el, binding){lettimer    el.addEventListener('click',() =>{if(timer) {clearTimeout(timer)      }      timer =setTimeout(() =>{        binding.value()      },1000)    })  },}exportdefaultdebounce复制代码

使用:给 Dom 加上v-debounce及回调函数即可

防抖exportdefault{methods: {    debounceClick () {console.log('只触发一次')    }  }}复制代码

v-emoji

背景:开发中遇到的表单输入,往往会有对输入内容的限制,比如不能输入表情和特殊字符,只能输入数字或字母等。

我们常规方法是在每一个表单的on-change事件上做处理。

exportdefault{methods: {vaidateEmoji(){varreg =/[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\s/gthis.note =this.note.replace(reg,'')      },    },  }复制代码

这样代码量比较大而且不好维护,所以我们需要自定义一个指令来解决这问题。

需求:根据正则表达式,设计自定义处理表单输入规则的指令,下面以禁止输入表情和特殊字符为例。

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){// 正则规则可根据需求自定义varregRule =/[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\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即可

复制代码

v-LazyLoad

背景:在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。

需求:实现一个图片懒加载指令,只加载浏览器可见区域的图片。

思路:

图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的

拿到所有的图片 Dom ,遍历每个图片判断当前图片是否到了可视区范围内

如果到了就设置图片的src属性,否则显示默认图片

图片懒加载有两种方式可以实现,一是绑定srcoll事件进行监听,二是使用IntersectionObserver判断图片是否到了可视区域,但是有浏览器兼容性问题。

下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持IntersectionObserverAPI,如果支持就使用IntersectionObserver实现懒加载,否则则使用srcoll事件监听 + 节流的方法实现。

constLazyLoad = {// install方法install(Vue, options){constdefaultSrc = options.default    Vue.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 = realSrc          el.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 = realSrc        el.removeAttribute('data-src')      }    }  },// 节流throttle(fn, delay){lettimerletprevTimereturnfunction(...args){constcurrTime =Date.now()constcontext =thisif(!prevTime) prevTime = currTimeclearTimeout(timer)if(currTime - prevTime > delay) {        prevTime = currTime        fn.apply(context, args)clearTimeout(timer)return}      timer =setTimeout(function(){        prevTime =Date.now()        timer =nullfn.apply(context, args)      }, delay)    }  },}exportdefaultLazyLoad复制代码

使用,将组件内

标签的src换成v-LazyLoad

need-to-insert-img

复制代码

v-permission

背景:在一些后台管理系统,我们可能需要根据用户角色进行一些操作权限的判断,很多时候我们都是粗暴地给一个元素添加v-if / v-show来进行显示隐藏,但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以通过全局自定义指令来处理。

需求:自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。

思路:

自定义一个权限数组

判断用户的权限是否在这个数组内,如果是则显示,否则则移除 Dom

functioncheckArray(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赋值判断即可

<!-- 显示 -->权限按钮1<!-- 不显示 -->权限按钮2复制代码

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 ||'16px Microsoft JhengHei'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复制代码

使用,设置水印文案,颜色,字体大小即可

复制代码

效果如图所示

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 即可

作者:lzg9527

链接:https://juejin.cn/post/6906028995133833230

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容