分享8個非常實用的Vue自定義指令

在 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)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內(nèi)容