在 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为对应变量。
本期分享结束,谢谢你们~~~