在 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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。