Vue 有一些很实用的指令 v-show
v-if
v-text
v-html
v-bind
v-on
可以帮助我们实现很复杂的功能,同时它还开辟了钩子供我们自己实现自定义指令。根据自己平时开发总结了一些可以通过指令实现的功能场景:
控制页面元素显示与隐藏,可用作控制权限功能
页面元素点击事件防抖与节流
通过自定义指令控制图片懒加载
针对页面特定元素添加自定义行为
对输入内容进行过滤
下面列举一些常见的自定义指令的实现代码:
权限指令
/**
* 权限指令
* @param {string} value 权限标识
* 例:<div v-permission="'editInfo'"></div>
*/
Vue.directive('permission', {
inserted: function (el, binding) {
const { value } = binding
// 在前置路由拦截获取权限按钮列表后存储在 store 中
const actionList = store.state.user.permission
if (value) {
const hasPermission = actionList .some(btnKey => btnKey === value)
// 没有权限直接移除 dom元素
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`需要指定权限标识! 如:v-permission="'editInfo'"`)
}
}
})
防抖指令
/**
* 防抖指令 单位时间只触发最后一次
* @param {Function} fn - 执行事件
* @param {?String|"click"} event - 事件类型 例:"click"
* @param {?Number|500} time - 间隔时间
* @param {Array} binding.value - [fn,event,time]
* 直接使用: <XXX v-debounce="reset]">刷新</XXX>
* 配置事件,间隔时间: <button v-debounce="[reset,'click',500]">刷新</button>
* 事件传递参数则: <button v-debounce="[()=>reset(param),`click`,500]">刷新</button>
*/
Vue.directive('debounce', {
bind: function (el, binding) {
try {
let fn, event = "click", time = 500;
if (typeof binding.value == 'function') {
fn = binding.value
} else {
[fn, event = "click", time = 500] = binding.value
}
let timer;
el.addEventListener(event, () => {
timer && clearTimeout(timer)
timer = setTimeout(() => fn(), time)
})
} catch (e) {
console.log(e)
}
}
})
节流指令
/**
* 节流指令 一段时间内首次触发时立即执行,此时间段内再次触发,不会执行!
* @param {Function} fn - 执行事件
* @param {?String|"click"} event - 事件类型 例:"click"
* @param {?Number|500} time - 间隔时间
* @param {Array} binding.value - [fn,event,time]
* 直接使用: <XXX v-throttle="reset]">刷新</XXX>
* 配置事件,间隔时间: <button v-throttle="[reset,'click',500]">刷新</button>
* 事件传递参数则: <button v-throttle="[()=>reset(param),`click`,500]">刷新</button>
*/
Vue.directive('throttle', {
bind: function (el, binding) {
let fn, event = "click", time = 1500;
if (typeof binding.value == 'function') {
fn = binding.value
} else {
[fn, event = "click", time = 1500] = binding.value
}
/**
* el.preTime 记录上次触发事件,
* 每次触发比较nowTime(当前时间) 和 el.preTime 的差是否大于指定的时间段!
*/
el.addEventListener(event, () => {
const nowTime = new Date().getTime()
if (!el.preTime || nowTime - el.preTime > time) {
el.preTime = nowTime
fn();
}
})
}
})
图片懒加载
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监听el
observe(el) {
var io = new IntersectionObserver((entries) => {
const realSrc = el.dataset.src
if (entries[0].isIntersecting) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
})
io.observe(el)
},
// 监听scroll事件
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll', () => {
handler(el)
})
}
给块元素增加背景
/**
* 使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
* 将其设置为背景图片,从而实现页面或组件水印效果
**/
Vue.directive('waterMarker', {
bind: function (el, binding) {
addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
}
}),
addWaterMarker(str, parentNode, font, textColor) {
// 水印文字,父元素,字体,文字颜色
var can = document.createElement('canvas')
parentNode.appendChild(can)
can.width = 200
can.height = 150
can.style.display = 'none'
var cans = 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') + ')'
}
页面元素拖拽指令
/**
* 设置需要拖拽的元素为相对定位,其父元素为绝对定位
* 鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值
* 鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
**/
Vue.directive('waterMarker', {
inserted: function (el) {
el.style.cursor = 'move'
el.onmousedown = function (e) {
let disx = e.pageX - el.offsetLeft
let disy = e.pageY - el.offsetTop
document.onmousemove = function (e) {
let x = e.pageX - disx
let y = e.pageY - disy
let maxX = document.body.clientWidth - parseInt(window.getComputedStyle(el).width)
let maxY = document.body.clientHeight - parseInt(window.getComputedStyle(el).height)
if (x < 0) {
x = 0
} else if (x > maxX) {
x = maxX
}
if (y < 0) {
y = 0
} else if (y > maxY) {
y = maxY
}
el.style.left = x + 'px'
el.style.top = y + 'px'
}
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null
}
}
},
})
输入框过滤特殊字符
let findEle = (parent, type) => {
return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type)
}
const trigger = (el, type) => {
const e = document.createEvent('HTMLEvents')
e.initEvent(type, true, true)
el.dispatchEvent(e)
}
Vue.directive('filterInput', {
bind: function (el, binding, vnode) {
// 正则规则可根据需求自定义
var regRule = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g
let $inp = findEle(el, 'input')
el.$inp = $inp
$inp.handle = function () {
let val = $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)
}
})
增加长按行为
实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件
/**
* 创建一个计时器, 2 秒后执行函数
* 当用户按下按钮时触发 mousedown 事件,启动计时器;用户松开按钮时调用 mouseout 事件。
* 如果 mouseup 事件 2 秒内被触发,就清除计时器,当作一个普通的点击事件
* 如果计时器没有在 2 秒内清除,则判定为一次长按,可以执行关联的函数。
* 在移动端要考虑 touchstart,touchend 事件
**/
Vue.directive('longpress', {
bind: function (el, binding, vNode) {
if (typeof binding.value !== 'function') {
throw 'callback must be a function'
}
// 定义变量
let pressTimer = null
// 创建计时器( 2秒后执行函数 )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
handler()
}, 2000)
}
}
// 取消计时器
let cancel = (e) => {
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
// 运行函数
const handler = (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.$value = value
},
// 指令与元素解绑的时候,移除事件绑定
unbind(el) {
el.removeEventListener('click', el.handler)
},
})
- END -