在Vue,除了核心功能默认内置的指令(vmodel和vshow),Vue也允许注册自定义指令。它的作用价值在于当开发人员在某些场景下需要对普通DOM元素进行操作。 Vue自定义指令有全局注册和局部注册两种方式。先来看看注册全局指令的方式,通过Vue。directive(id,〔definition〕)方式注册全局指令。然后在入口文件中进行Vue。use()调用。 批量注册指令,新建directivesindex。js文件importcopyfrom。copyimportlongpressfrom。longpress自定义指令constdirectives{copy,longpress,}exportdefault{install(Vue){Object。keys(directives)。forEach((key){Vue。directive(key,directives〔key〕)})},} 在main。js引入并调用importVuefromvueimportDirectivesfrom。JSdirectivesVue。use(Directives) 指令定义函数提供了几个钩子函数(可选):bind: 只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作。inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值。componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。unbind: 只调用一次,指令与元素解绑时调用。 下面分享几个实用的Vue自定义指令复制粘贴指令vcopy长按指令vlongpress输入框防抖指令vdebounce禁止表情及特殊字符vemoji图片懒加载vLazyLoad权限校验指令vpremission实现页面水印vwaterMarker拖拽指令vdraggablevcopy 需求:实现一键复制文本内容,用于鼠标右键粘贴。 思路:动态创建textarea标签,并设置readOnly属性及移出可视区域 将要复制的值赋给textarea标签的value属性,并插入到body 选中值textarea并复制 将body中插入的textarea移除 在第一次调用时绑定事件,在解绑时移除事件constcopy{bind(el,{value}){el。valuevalueel。handler(){if(!el。value){值为空的时候,给出提示。可根据项目UI仔细设计console。log(无复制内容)return}动态创建textarea标签consttextareadocument。createElement(textarea)将该textarea设为readonly防止iOS下自动唤起键盘,同时将textarea移出可视区域textarea。readOnlyreadonlytextarea。style。positionabsolutetextarea。style。left9999px将要copy的值赋给textarea标签的value属性textarea。valueel。value将textarea插入到body中document。body。appendChild(textarea)选中值并复制textarea。select()constresultdocument。execCommand(Copy)if(result){console。log(复制成功)可根据项目UI仔细设计}document。body。removeChild(textarea)}绑定点击事件,就是所谓的一键copy啦el。addEventListener(click,el。handler)},当传进来的值更新的时候触发componentUpdated(el,{value}){el。valuevalue},指令与元素解绑的时候,移除事件绑定unbind(el){el。removeEventListener(click,el。handler)},}exportdefaultcopy 使用:给Dom加上vcopy复制文本即可templatebuttonvcopycopyText复制buttontemplatevlongpress 需求:实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件 思路:创建一个计时器,2秒后执行函数 当用户按下按钮时触发mousedown事件,启动计时器;用户松开按钮时调用mouseout事件。 如果mouseup事件2秒内被触发,就清除计时器,当作一个普通的点击事件 如果计时器没有在2秒内清除,则判定为一次长按,可以执行关联的函数。 在移动端要考虑touchstart,touchend事件constlongpress{bind:function(el,binding,vNode){if(typeofbinding。value!function){throwcallbackmustbeafunction}定义变量letpressTimernull创建计时器(2秒后执行函数)letstart(e){if(e。typeclicke。button!0){return}if(pressTimernull){pressTimersetTimeout((){handler()},2000)}}取消计时器letcancel(e){if(pressTimer!null){clearTimeout(pressTimer)pressTimernull}}运行函数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。valuevalue},指令与元素解绑的时候,移除事件绑定unbind(el){el。removeEventListener(click,el。handler)},}exportdefaultlongpress 使用:给Dom加上vlongpress回调函数即可templatebuttonvlongpresslongpress长按buttontemplatevdebounce 背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如新增表单的提交按钮,多次点击就会新增多条重复的数据。 需求:防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。 思路:定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。 将时间绑定在click方法上。constdebounce{inserted:function(el,binding){lettimerel。addEventListener(keyup,(){if(timer){clearTimeout(timer)}timersetTimeout((){binding。value()},1000)})},}exportdefaultdebounce 使用:给Dom加上vdebounce回调函数即可templatebuttonvdebouncedebounceClick防抖buttontemplatevemoji 背景:开发中遇到的表单输入,往往会有对输入内容的限制,比如不能输入表情和特殊字符,只能输入数字或字母等。 我们常规方法是在每一个表单的onchange事件上做处理。templateinputtypetextvmodelnotechangevaidateEmojitemplate 这样代码量比较大而且不好维护,所以我们需要自定义一个指令来解决这问题。 需求:根据正则表达式,设计自定义处理表单输入规则的指令,下面以禁止输入表情和特殊字符为例。letfindEle(parent,type){returnparent。tagName。toLowerCase()type?parent:parent。querySelector(type)}consttrigger(el,type){constedocument。createEvent(HTMLEvents)e。initEvent(type,true,true)el。dispatchEvent(e)}constemoji{bind:function(el,binding,vnode){正则规则可根据需求自定义varregRule〔u4E00u9FA5dazAZrns,。?!,。?!(){}〔〕〕sgletinpfindEle(el,input)el。inpinpinp。handlefunction(){letvalinp。valueinp。valueval。replace(regRule,)trigger(inp,input)}inp。addEventListener(keyup,inp。handle)},unbind:function(el){el。inp。removeEventListener(keyup,el。inp。handle)},}exportdefaultemoji 使用:将需要校验的输入框加上vemoji即可templateinputtypetextvmodelnotevemojitemplatevLazyLoad 背景:在类电商类项目,往往存在大量的图片,如banner广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。 需求:实现一个图片懒加载指令,只加载浏览器可见区域的图片。 思路:图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的 拿到所有的图片Dom,遍历每个图片判断当前图片是否到了可视区范围内 如果到了就设置图片的src属性,否则显示默认图片 图片懒加载有两种方式可以实现,一是绑定srcoll事件进行监听,二是使用IntersectionObserver判断图片是否到了可视区域,但是有浏览器兼容性问题。 下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持IntersectionObserverAPI,如果支持就使用IntersectionObserver实现懒加载,否则则使用srcoll事件监听节流的方法实现。constLazyLoad{install方法install(Vue,options){constdefaultSrcoptions。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(datasrc,val)el。setAttribute(src,def)},利用IntersectionObserver监听elobserve(el){varionewIntersectionObserver((entries){constrealSrcel。dataset。srcif(entries〔0〕。isIntersecting){if(realSrc){el。srcrealSrcel。removeAttribute(datasrc)}}})io。observe(el)},监听scroll事件listenerScroll(el){consthandlerLazyLoad。throttle(LazyLoad。load,300)LazyLoad。load(el)window。addEventListener(scroll,(){handler(el)})},加载真实图片load(el){constwindowHeightdocument。documentElement。clientHeightconstelTopel。getBoundingClientRect()。topconstelBtmel。getBoundingClientRect()。bottomconstrealSrcel。dataset。srcif(elTopwindowHeight0elBtm0){if(realSrc){el。srcrealSrcel。removeAttribute(datasrc)}}},节流throttle(fn,delay){lettimerletprevTimereturnfunction(。。。args){constcurrTimeDate。now()constcontextthisif(!prevTime)prevTimecurrTimeclearTimeout(timer)if(currTimeprevTimedelay){prevTimecurrTimefn。apply(context,args)clearTimeout(timer)return}timersetTimeout(function(){prevTimeDate。now()timernullfn。apply(context,args)},delay)}},}exportdefaultLazyLoad 使用,将组件内标签的src换成vLazyLoadimgvLazyLoadxxx。jpgvpermission 背景:在一些后台管理系统,我们可能需要根据用户角色进行一些操作权限的判断,很多时候我们都是粗暴地给一个元素添加vifvshow来进行显示隐藏,但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以通过全局自定义指令来处理。 需求:自定义一个权限指令,对需要权限判断的Dom进行显示隐藏。 思路:自定义一个权限数组 判断用户的权限是否在这个数组内,如果是则显示,否则则移除DomfunctioncheckArray(key){letarr〔1,2,3,4〕letindexarr。indexOf(key)if(index1){returntrue有权限}else{returnfalse无权限}}constpermission{inserted:function(el,binding){letpermissionbinding。value获取到vpermission的值if(permission){lethasPermissioncheckArray(permission)if(!hasPermission){没有权限移除Dom元素el。parentNodeel。parentNode。removeChild(el)}}},}exportdefaultpermission 使用:给vpermission赋值判断即可!显示buttonvpermission1权限按钮1button!不显示buttonvpermission10权限按钮2buttonvuewaterMarker 需求:给整个页面添加背景水印 思路:使用canvas特性生成base64格式的图片文件,设置其字体大小,颜色等。 将其设置为背景图片,从而实现页面或组件水印效果functionaddWaterMarker(str,parentNode,font,textColor){水印文字,父元素,字体,文字颜色varcandocument。createElement(canvas)parentNode。appendChild(can)can。width200can。height150can。style。displaynonevarcanscan。getContext(2d)cans。rotate((20Math。PI)180)cans。fontfont16pxMicrosoftJhengHeicans。fillStyletextColorrgba(180,180,180,0。3)cans。textAlignleftcans。textBaselineMiddlecans。fillText(str,can。width10,can。height2)parentNode。style。backgroundImageurl(can。toDataURL(imagepng))}constwaterMarker{bind:function(el,binding){addWaterMarker(binding。value。text,el,binding。value。font,binding。value。textColor)},}exportdefaultwaterMarker 使用,设置水印文案,颜色,字体大小即可templatetemplatevdraggable 需求:实现一个拖拽指令,可在页面可视区域任意拖拽元素。 思路:设置需要拖拽的元素为相对定位,其父元素为绝对定位。 鼠标按下(onmousedown)时记录目标元素当前的left和top值。 鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的left和top值 鼠标松开(onmouseup)时完成一次拖拽constdraggable{inserted:function(el){el。style。cursormoveel。onmousedownfunction(e){letdisxe。pageXel。offsetLeftletdisye。pageYel。offsetTopdocument。onmousemovefunction(e){letxe。pageXdisxletye。pageYdisyletmaxXdocument。body。clientWidthparseInt(window。getComputedStyle(el)。width)letmaxYdocument。body。clientHeightparseInt(window。getComputedStyle(el)。height)if(x0){x0}elseif(xmaxX){xmaxX}if(y0){y0}elseif(ymaxY){ymaxY}el。style。leftxpxel。style。topypx}document。onmouseupfunction(){document。onmousemovedocument。onmouseupnull}}},}exportdefaultdraggable 使用:在Dom上加上vdraggable即可templatetemplate