在 Vue2.0,除了核心功能默認內置的指令 ( v-model 和 v-show ),Vue 也容許註冊自定義指令。在 Vue2.0 中,代碼複用和抽象的主要形式是組件。然而,有的狀況下,你仍然須要對普通 DOM 元素進行底層操做,這時候就會用到自定義指令。javascript
Vue 自定義指令有全局註冊和局部註冊兩種方式。全局註冊指令的方式,經過 Vue.directive( id, [definition] )
方式註冊全局指令。若是想註冊局部指令,組件中也接受一個directives
的選項。css
// 註冊一個全局自定義指令 `v-focus`
Vue.directive('focus', {
// 當被綁定的元素插入到 DOM 中時……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
// 註冊一個局部自定義指令 `v-focus`
directives: {
focus: {
// 指令的定義
inserted: function (el) {
el.focus()
}
}
}
複製代碼
而後咱們能夠在模板中任何元素上使用心得v-focus
property,以下:html
<input v-focus>
複製代碼
當咱們須要批量註冊自定義指令時,寫不少個``Vue.directive( id, [definition] ) 會致使代碼冗餘,因此咱們能夠利用
Vue.use()` 的特性,完成批量註冊。前端
批量註冊指令,新建 directives/directive.js
文件vue
// 導入指令定義文件
import debounce from './debounce'
import throttle from './throttle'
// 集成一塊兒
const directives = {
debounce,
throttle,
}
//批量註冊
export default {
install(Vue) {
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key])
})
},
}
複製代碼
在 main.js
引入,並Vue.use()
調用完成批量註冊。java
import Vue from 'vue'
import Directives from './directives/directive.js'
Vue.use(Directives)
複製代碼
一個指令定義對象能夠提供以下幾個鉤子函數 (均爲可選):node
接下來咱們來看一下鉤子函數的參數 (即 el
、binding
、vnode
和 oldVnode
)。express
指令鉤子函數會被傳入如下參數:後端
el
:指令所綁定的元素,能夠用來直接操做 DOM。binding
:一個對象,包含如下 property:
name
:指令名,不包括 v-
前綴。value
:指令的綁定值,例如:v-my-directive="1 + 1"
中,綁定值爲 2
。oldValue
:指令綁定的前一個值,僅在 update
和 componentUpdated
鉤子中可用。不管值是否改變均可用。expression
:字符串形式的指令表達式。例如 v-my-directive="1 + 1"
中,表達式爲 "1 + 1"
。arg
:傳給指令的參數,可選。例如 v-my-directive:foo
中,參數爲 "foo"
。modifiers
:一個包含修飾符的對象。例如:v-my-directive.foo.bar
中,修飾符對象爲 { foo: true, bar: true }
。vnode
:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。oldVnode
:上一個虛擬節點,僅在 update
和 componentUpdated
鉤子中可用。注意:除了 el
以外,其它參數都應該是隻讀的,切勿進行修改。若是須要在鉤子之間共享數據,建議經過元素的 dataset
來進行。api
下面分享幾個實用的 Vue 自定義指令
v-longpress
v-debounce
v-throttle
v-click-out
v-scroll-pop
v-sensor
需求:當用戶按下鼠標左鍵或移動端單指觸碰,並按住按鈕幾秒鐘時,視爲一次長按,觸發對應的函數。
思路:
mousedown
或touchstart
事件,啓動計時器。click
、 mouseup
、touchend
或 touchcancel
事件在 n 秒內被觸發,則清除計時器,視爲普通點擊事件。const longpress = {
bind: function (el, {value:{fn,time}}) {
//沒綁定函數直接返回
if (typeof fn !== 'function') return
// 定義定時器變量
el._timer = null
// 建立計時器( n秒後執行函數 )
el._start = (e) => {
//e.type表示觸發的事件類型如mousedown,touchstart等
//pc端: e.button表示是哪一個鍵按下0爲鼠標左鍵,1爲中鍵,2爲右鍵
//移動端: e.touches表示同時按下的鍵爲個數
if ( (e.type === 'mousedown' && e.button && e.button !== 0) ||
(e.type === 'touchstart' && e.touches && e.touches.length > 1)
) return;
//定時長按n秒後執行事件
if (el._timer === null) {
el._timer = setTimeout(() => {
fn()
}, time)
//取消瀏覽器默認事件,如右鍵彈窗
el.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
}
}
// 若是兩秒內鬆手,則取消計時器
el._cancel = (e) => {
if (el._timer !== null) {
clearTimeout(el._timer)
el._timer = null
}
}
// 添加計時監聽
el.addEventListener('mousedown', el._start)
el.addEventListener('touchstart', el._start)
// 添加取消監聽
el.addEventListener('click', el._cancel)
el.addEventListener('mouseout', el._cancel)
el.addEventListener('touchend', el._cancel)
el.addEventListener('touchcancel', el._cancel)
},
// 指令與元素解綁時,移除事件綁定
unbind(el) {
// 移除計時監聽
el.removeEventListener('mousedown', el._start)
el.removeEventListener('touchstart', el._start)
// 移除取消監聽
el.removeEventListener('click', el._cancel)
el.removeEventListener('mouseout', el._cancel)
el.removeEventListener('touchend', el._cancel)
el.removeEventListener('touchcancel', el._cancel)
},
}
export default longpress
複製代碼
使用:給 Dom 加上 v-longpress
及參數便可
<template>
<button v-longpress="{fn: longpress,time:2000}">長按</button>
</template>
<script> export default { methods: { longpress () { console.log('長按指令生效') } } } </script>
複製代碼
背景:在開發中,有時遇到要給input或者滾動條添加監聽事件,須要作防抖處理。
需求:防止input或scroll事件在短期內被屢次觸發,使用防抖函數限制必定時間後觸發。
思路:
const debounce = {
inserted: function (el, {value:{fn, event, time}}) {
//沒綁定函數直接返回
if (typeof fn !== 'function') return
el._timer = null
//監聽點擊事件,限定事件內若是再次點擊則清空定時器並從新定時
el.addEventListener(event, () => {
if (el._timer !== null) {
clearTimeout(el._timer)
el._timer = null
}
el._timer = setTimeout(() => {
fn()
}, time)
})
},
}
export default debounce
複製代碼
使用:給 Dom 加上 v-debounce
及回調函數便可
<template>
<input v-debounce="{fn: debounce, event: 'input', time: 5000}" />
<div v-debounce="{fn: debounce, event: 'scroll', time: 5000}">
<p>文字文字文字文字...</p>
</div>
</template>
<script> export default { methods: { debounce(){ console.log('debounce 防抖') }, } } </script>
複製代碼
背景:在開發中,有些提交保存按鈕有時候會在短期內被點擊屢次,這樣就會屢次重複請求後端接口,形成數據的混亂,好比當即購買按鈕,屢次點擊就會屢次調用建立訂單接口。
需求:防止按鈕在短期內被屢次點擊,使用節流函數限制規定時間內只能點擊一次。
思路:
const throttle = {
bind:function (el,{value:{fn,time}}) {
if (typeof fn !== 'function') return
el._flag = true;//開關默認爲開
el._timer = null
el.handler = function () {
if (!el._flag) return;
//執行以後開關關閉
el._flag && fn()
el._flag = false
if (el._timer !== null) {
clearTimeout(el._timer)
el._timer = null
}
el._timer = setTimeout(() => {
el._flag = true;//三秒後開關開啓
}, time);
}
el.addEventListener('click',el.handler)
},
unbind:function (el,binding) {
el.removeEventListener('click',el.handler)
}
}
export default throttle
複製代碼
使用:給Dom加上v-throttle
及回調函數便可。
<template>
<button v-throttle="{fn: throttle,time:3000}">throttle節流</button>
</template>
<script> export default { methods: { throttle () { console.log('throttle 節流 只觸發一次') } } } </script>
複製代碼
背景:在咱們的項目裏,常常會出現一個彈窗,須要點擊彈窗外部關閉該彈窗。
需求:實現一個指令,點擊目標區域外部,觸發指定函數。
思路:
const clickOut = {
bind(el,{value}){
function clickHandler(e) {
//先判斷點擊的元素是不是自己,若是是自己,則返回
if (el.contains(e.target)) return;
//判斷指令中是否綁定了函數
if (typeof value === 'function') {
//若是綁定了函數,則調用函數,此處value就是clickImgOut方法
value()
}
}
// 給當前元素綁定個私有變量,方便在unbind中能夠解除事件監聽
el.handler = clickHandler;
//添加事件監聽
setTimeout(() => {
document.addEventListener('click',el.handler);
}, 0);
},
unbind(el){
//解除事件監聽
document.removeEventListener('click',el.handler);
}
}
export default clickOut
複製代碼
使用,將須要用到該指令的元素添加 v-click-out
<template>
<div>
<button @click="isImgShow = true">展現彈窗</button>
<div v-click-out="clickImgOut" v-if="isImgShow" class="pop">
<img src="https://xxx.jpg" alt="">
<p>文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字文字</p>
</div>
</div>
</template>
<script> export default { data(){ return { isImgShow : false } }, methods:{ clickImgOut(){ this.isImgShow = false; console.log('點擊彈窗外部') } } } </script>
複製代碼
背景:在咱們的項目中,常用彈窗展現活動規則,活動規則過長鬚要滾動時,時長會致使外部滾動。這時針對這種狀況,咱們能夠經過全局自定義指令來處理。
需求:自定義一個指令,使得彈窗內部內容能夠滾動,外部沒法滾動。
思路:
const scrollPop = {
bind(el) {
//定義此時到元素的內容垂直滾動的距離
el.st = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
let cssStr = `overflow: hidden;width: 100%; height: 100%; position: fixed; top: ${- el.st}px;`
document.querySelector('html').cssText = cssStr
document.body.style.cssText = cssStr
},
unbind(el,{value}) {
let cssStr = 'overflow: auto; height: 100%; position: relative; top: 0px;scroll-behavior: auto'
document.querySelector('html').cssText = cssStr
document.body.style.cssText = cssStr
document.querySelector('html').style.scrollBehavior = 'auto'
//手動設置滾動距離
document.documentElement.scrollTop = el.st
document.body.scrollTop = el.st
if (value !== 'smooth')return;
//若是傳了滾動方式爲smooth平穩滾動即有感滾動,當滾動完畢後,把auto改回smooth
let timer = setTimeout(() => {
cssStr = `overflow: auto; height: 100%; position: relative; top: 0px; scroll-behavior: ${value||'smooth'}`
document.querySelector('html').cssText = cssStr
document.querySelector('html').style.scrollBehavior = value || 'smooth'
document.body.style.cssText = cssStr
}, 1);
}
}
export default scrollPop
複製代碼
使用:給須要限制的彈窗綁定v-scroll-pop
屬性,並設置scroll-behavior
值便可。
<div class="scroll-pop" v-if="isScrollPopShow" v-scroll-pop="'smooth'">
<div class="content">
<p>這是很長一段文字,請耐心讀完,而後你會發現這段文字並無什麼意義。</p>
...
</div>
</div>
複製代碼
背景:目前前端埋點代碼大量入侵業務,埋點代碼量大且難以區分和維護,現作出優化方案以減小其代碼量。
註冊封裝自定義指令的代碼
const sensor = {
// 當被綁定的元素插入到 DOM 中時
inserted: function (el,{value: sensorObj}) {
let showObj={} ,clickObj={}//showObj表明展現類埋點,clickObj表明點擊類埋點
//若是傳入參數格式不爲對象,則不向下執行
if (!Object.prototype.toString.call(sensorObj) === '[object Object]'|| JSON.stringify(sensorObj) == "{}") return
//遍歷傳入對象參數,根據key值肯定埋點類型
for (const key in sensorObj) {
if (Object.hasOwnProperty.call(sensorObj, key)) {
switch (key) {
case 'el':
showObj= {
name:'ElementShow',
value: sensorObj[key]
};
break;
case 'pop':
showObj= {
name:'PopupTrack',
value: sensorObj[key]
};
break;
case 'elClick':
clickObj= {
name:'$WebClick',
value: sensorObj[key]
};
break;
case 'popClick':
clickObj= {
name:'PopupBtnClick',
value: sensorObj[key]
};
break;
default:
break;
}
}
}
// 展現類埋點執行
showObj.value && sensors.track(showObj.name, {
FileName: showObj.value
});
//點擊類埋點執行
if (clickObj.value) {
el.handler = function () {
clickObj.name === '$WebClick' && sensors.track(clickObj.name, {
$element_name: clickObj.value
});
clickObj.name === 'PopupBtnClick' && sensors.track(clickObj.name, {
FileName: clickObj.value
});
}
el.addEventListener('click',el.handler)
}
},
// 指令與元素解綁的時候,移除事件綁定
unbind(el) {
el.handler && el.removeEventListener('click', el.handler)
}
}
export default sensor
複製代碼
對於除自定義事件之外的埋點事件,較好的優化辦法就是使用自定義指令。使用 v-sensor=" {el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'} " 。v-sensor接收一個對象做爲參數,對象的key爲事件標識,對象的value爲事件屬性,key值具體對應關係以下。
//單獨使用ElementShow或$WebClick
<div v-sensor="{el :'Btn_XXX_Tag_CXXXon'}">我是一個麼得感情的標籤</div>
<div v-sensor="{elClick:'Btn_XXX_Tag_Common'}">俺也同樣</div>
//ElementShow和$WebClick組合使用方法
<div v-sensor="{el :'Btn_XXX_Tag_Common',elClick:'Btn_XXX_Tag_Common'}">俺也同樣</div>
//單獨使用PopupTrack和PopupBtnClick
<div v-sensor="{pop :'Pop_XXX_Tag_Common'}">俺也同樣</div>
<div v-sensor="{popClick:'Pop_XXX_Tag_Common'}">俺也同樣</div>
//PopupTrack和PopupBtnClick組合使用方法
<div v-sensor="{pop :'Pop_XXX_Tag_Common',popClick:'Pop_XXX_Tag_Common'}">俺也同樣</div>
//變量使用方法
<div v-sensor="{pop :`${sensorVal}`}">俺也同樣</div>
複製代碼
提示:
因爲該自定義指令是在元素插入頁面DOM中時執行的,因此若是事件屬性值使用變量的話,請在created生命週期內操做完畢,或給該元素綁定v-if爲對應變量。
本期分享結束,謝謝你們~~~