在 Vue,除了核心功能默認(rèn)內(nèi)置的指令 ( v-model 和 v-show ),Vue 也允許注冊自定義指令。它的作用價值在于當(dāng)開發(fā)人員在某些場景下需要對普通 DOM 元素進(jìn)行操作。
Vue 自定義指令有全局注冊和局部注冊兩種方式。先來看看注冊全局指令的方式,通過Vue.directive( id, [definition] )方式注冊全局指令。然后在入口文件中進(jìn)行Vue.use()調(diào)用。
批量注冊指令,新建directives/index.js文件
importcopyfrom'./copy'importlongpressfrom'./longpress'// 自定義指令constdirectives = {? copy,? longpress,}exportdefault{install(Vue){Object.keys(directives).forEach((key) =>{? ? ? Vue.directive(key, directives[key])? ? })? },}復(fù)制代碼
在main.js引入并調(diào)用
importVuefrom'vue'importDirectivesfrom'./JS/directives'Vue.use(Directives)復(fù)制代碼
指令定義函數(shù)提供了幾個鉤子函數(shù)(可選):
bind: 只調(diào)用一次,指令第一次綁定到元素時調(diào)用,可以定義一個在綁定時執(zhí)行一次的初始化動作。
inserted: 被綁定元素插入父節(jié)點時調(diào)用(父節(jié)點存在即可調(diào)用,不必存在于 document 中)。
update: 被綁定元素所在的模板更新時調(diào)用,而不論綁定值是否變化。通過比較更新前后的綁定值。
componentUpdated: 被綁定元素所在模板完成一次更新周期時調(diào)用。
unbind: 只調(diào)用一次, 指令與元素解綁時調(diào)用。
下面分享幾個實用的 Vue 自定義指令
復(fù)制粘貼指令v-copy
長按指令v-longpress
輸入框防抖指令v-debounce
禁止表情及特殊字符v-emoji
圖片懶加載v-LazyLoad
權(quán)限校驗指令v-premission
實現(xiàn)頁面水印v-waterMarker
拖拽指令v-draggable
v-copy
需求:實現(xiàn)一鍵復(fù)制文本內(nèi)容,用于鼠標(biāo)右鍵粘貼。
思路:
動態(tài)創(chuàng)建textarea標(biāo)簽,并設(shè)置readOnly屬性及移出可視區(qū)域
將要復(fù)制的值賦給textarea標(biāo)簽的value屬性,并插入到body
選中值textarea并復(fù)制
將body中插入的textarea移除
在第一次調(diào)用時綁定事件,在解綁時移除事件
constcopy = {bind(el, { value }){? ? el.$value = value? ? el.handler =() =>{if(!el.$value) {// 值為空的時候,給出提示。可根據(jù)項目UI仔細(xì)設(shè)計console.log('無復(fù)制內(nèi)容')return}// 動態(tài)創(chuàng)建 textarea 標(biāo)簽consttextarea =document.createElement('textarea')// 將該 textarea 設(shè)為 readonly 防止 iOS 下自動喚起鍵盤,同時將 textarea 移出可視區(qū)域textarea.readOnly ='readonly'textarea.style.position ='absolute'textarea.style.left ='-9999px'// 將要 copy 的值賦給 textarea 標(biāo)簽的 value 屬性textarea.value = el.$value// 將 textarea 插入到 body 中document.body.appendChild(textarea)// 選中值并復(fù)制textarea.select()constresult =document.execCommand('Copy')if(result) {console.log('復(fù)制成功')// 可根據(jù)項目UI仔細(xì)設(shè)計}document.body.removeChild(textarea)? ? }// 綁定點擊事件,就是所謂的一鍵 copy 啦el.addEventListener('click', el.handler)? },// 當(dāng)傳進(jìn)來的值更新的時候觸發(fā)componentUpdated(el, { value }){? ? el.$value = value? },// 指令與元素解綁的時候,移除事件綁定unbind(el){? ? el.removeEventListener('click', el.handler)? },}exportdefaultcopy復(fù)制代碼
使用:給 Dom 加上v-copy及復(fù)制的文本即可
復(fù)制exportdefault{data(){return{copyText:'a copy directives',? ? ? }? ? },? }復(fù)制代碼
v-longpress
需求:實現(xiàn)長按,用戶需要按下并按住按鈕幾秒鐘,觸發(fā)相應(yīng)的事件
思路:
創(chuàng)建一個計時器, 2 秒后執(zhí)行函數(shù)
當(dāng)用戶按下按鈕時觸發(fā)mousedown事件,啟動計時器;用戶松開按鈕時調(diào)用 mouseout事件。
如果mouseup事件 2 秒內(nèi)被觸發(fā),就清除計時器,當(dāng)作一個普通的點擊事件
如果計時器沒有在 2 秒內(nèi)清除,則判定為一次長按,可以執(zhí)行關(guān)聯(lián)的函數(shù)。
在移動端要考慮touchstart,touchend事件
constlongpress = {bind:function(el, binding, vNode){if(typeofbinding.value !=='function') {throw'callback must be a function'}// 定義變量letpressTimer =null// 創(chuàng)建計時器( 2秒后執(zhí)行函數(shù) )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}? ? }// 運(yùn)行函數(shù)consthandler =(e) =>{? ? ? binding.value(e)? ? }// 添加事件監(jiān)聽器el.addEventListener('mousedown', start)? ? el.addEventListener('touchstart', start)// 取消計時器el.addEventListener('click', cancel)? ? el.addEventListener('mouseout', cancel)? ? el.addEventListener('touchend', cancel)? ? el.addEventListener('touchcancel', cancel)? },// 當(dāng)傳進(jìn)來的值更新的時候觸發(fā)componentUpdated(el, { value }){? ? el.$value = value? },// 指令與元素解綁的時候,移除事件綁定unbind(el){? ? el.removeEventListener('click', el.handler)? },}exportdefaultlongpress復(fù)制代碼
使用:給 Dom 加上v-longpress及回調(diào)函數(shù)即可
長按exportdefault{methods: {? ? longpress () {? ? ? alert('長按指令生效')? ? }? }}復(fù)制代碼
v-debounce
背景:在開發(fā)中,有些提交保存按鈕有時候會在短時間內(nèi)被點擊多次,這樣就會多次重復(fù)請求后端接口,造成數(shù)據(jù)的混亂,比如新增表單的提交按鈕,多次點擊就會新增多條重復(fù)的數(shù)據(jù)。
需求:防止按鈕在短時間內(nèi)被多次點擊,使用防抖函數(shù)限制規(guī)定時間內(nèi)只能點擊一次。
思路:
定義一個延遲執(zhí)行的方法,如果在延遲時間內(nèi)再調(diào)用該方法,則重新計算執(zhí)行時間。
將事件綁定在 click 方法上。
constdebounce = {inserted:function(el, binding){lettimer? ? el.addEventListener('click',() =>{if(timer) {clearTimeout(timer)? ? ? }? ? ? timer =setTimeout(() =>{? ? ? ? binding.value()? ? ? },1000)? ? })? },}exportdefaultdebounce復(fù)制代碼
使用:給 Dom 加上v-debounce及回調(diào)函數(shù)即可
防抖exportdefault{methods: {? ? debounceClick () {console.log('只觸發(fā)一次')? ? }? }}復(fù)制代碼
v-emoji
背景:開發(fā)中遇到的表單輸入,往往會有對輸入內(nèi)容的限制,比如不能輸入表情和特殊字符,只能輸入數(shù)字或字母等。
我們常規(guī)方法是在每一個表單的on-change事件上做處理。
exportdefault{methods: {vaidateEmoji(){varreg =/[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\s/gthis.note =this.note.replace(reg,'')? ? ? },? ? },? }復(fù)制代碼
這樣代碼量比較大而且不好維護(hù),所以我們需要自定義一個指令來解決這問題。
需求:根據(jù)正則表達(dá)式,設(shè)計自定義處理表單輸入規(guī)則的指令,下面以禁止輸入表情和特殊字符為例。
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){// 正則規(guī)則可根據(jù)需求自定義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復(fù)制代碼
使用:將需要校驗的輸入框加上v-emoji即可
復(fù)制代碼
v-LazyLoad
背景:在類電商類項目,往往存在大量的圖片,如 banner 廣告圖,菜單導(dǎo)航圖,美團(tuán)等商家列表頭圖等。圖片眾多以及圖片體積過大往往會影響頁面加載速度,造成不良的用戶體驗,所以進(jìn)行圖片懶加載優(yōu)化勢在必行。
需求:實現(xiàn)一個圖片懶加載指令,只加載瀏覽器可見區(qū)域的圖片。
思路:
圖片懶加載的原理主要是判斷當(dāng)前圖片是否到了可視區(qū)域這一核心邏輯實現(xiàn)的
拿到所有的圖片 Dom ,遍歷每個圖片判斷當(dāng)前圖片是否到了可視區(qū)范圍內(nèi)
如果到了就設(shè)置圖片的src屬性,否則顯示默認(rèn)圖片
圖片懶加載有兩種方式可以實現(xiàn),一是綁定srcoll事件進(jìn)行監(jiān)聽,二是使用IntersectionObserver判斷圖片是否到了可視區(qū)域,但是有瀏覽器兼容性問題。
下面封裝一個懶加載指令兼容兩種方法,判斷瀏覽器是否支持IntersectionObserverAPI,如果支持就使用IntersectionObserver實現(xiàn)懶加載,否則則使用srcoll事件監(jiān)聽 + 節(jié)流的方法實現(xiàn)。
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監(jiān)聽elobserve(el){vario =newIntersectionObserver((entries) =>{constrealSrc = el.dataset.srcif(entries[0].isIntersecting) {if(realSrc) {? ? ? ? ? el.src = realSrc? ? ? ? ? el.removeAttribute('data-src')? ? ? ? }? ? ? }? ? })? ? io.observe(el)? },// 監(jiān)聽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')? ? ? }? ? }? },// 節(jié)流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復(fù)制代碼
使用,將組件內(nèi)
標(biāo)簽的src換成v-LazyLoad
need-to-insert-img
復(fù)制代碼
v-permission
背景:在一些后臺管理系統(tǒng),我們可能需要根據(jù)用戶角色進(jìn)行一些操作權(quán)限的判斷,很多時候我們都是粗暴地給一個元素添加v-if / v-show來進(jìn)行顯示隱藏,但如果判斷條件繁瑣且多個地方需要判斷,這種方式的代碼不僅不優(yōu)雅而且冗余。針對這種情況,我們可以通過全局自定義指令來處理。
需求:自定義一個權(quán)限指令,對需要權(quán)限判斷的 Dom 進(jìn)行顯示隱藏。
思路:
自定義一個權(quán)限數(shù)組
判斷用戶的權(quán)限是否在這個數(shù)組內(nèi),如果是則顯示,否則則移除 Dom
functioncheckArray(key){letarr = ['1','2','3','4']letindex = arr.indexOf(key)if(index > -1) {returntrue// 有權(quán)限}else{returnfalse// 無權(quán)限}}constpermission = {inserted:function(el, binding){letpermission = binding.value// 獲取到 v-permission的值if(permission) {lethasPermission = checkArray(permission)if(!hasPermission) {// 沒有權(quán)限 移除Dom元素el.parentNode && el.parentNode.removeChild(el)? ? ? }? ? }? },}exportdefaultpermission復(fù)制代碼
使用:給v-permission賦值判斷即可
<!-- 顯示 -->權(quán)限按鈕1<!-- 不顯示 -->權(quán)限按鈕2復(fù)制代碼
vue-waterMarker
需求:給整個頁面添加背景水印
思路:
使用canvas特性生成base64格式的圖片文件,設(shè)置其字體大小,顏色等。
將其設(shè)置為背景圖片,從而實現(xiàn)頁面或組件水印效果
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復(fù)制代碼
使用,設(shè)置水印文案,顏色,字體大小即可
復(fù)制代碼
效果如圖所示
v-draggable
需求:實現(xiàn)一個拖拽指令,可在頁面可視區(qū)域任意拖拽元素。
思路:
設(shè)置需要拖拽的元素為相對定位,其父元素為絕對定位。
鼠標(biāo)按下(onmousedown)時記錄目標(biāo)元素當(dāng)前的left和top值。
鼠標(biāo)移動(onmousemove)時計算每次移動的橫向距離和縱向距離的變化值,并改變元素的left和top值
鼠標(biāo)松開(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復(fù)制代碼
使用: 在 Dom 上加上 v-draggable 即可
作者:lzg9527
鏈接:https://juejin.cn/post/6906028995133833230
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。