共计 4835 个字符,预计需要花费 13 分钟才能阅读完成。
Vue 有一些很实用的指令 v-show
v-if
v-text
v-html
v-bind
v-on
可以帮助我们实现很复杂的功能,同时它还开辟了钩子供我们自己实现自定义指令。根据自己平时开发总结了一些可以通过指令实现的功能场景:
- 控制页面元素显示与隐藏,可用作控制权限功能
- 页面元素点击事件防抖与节流
- 通过自定义指令控制图片懒加载
- 针对页面特定元素添加自定义行为
- 对输入内容进行过滤
下面列举一些常见的自定义指令的实现代码:
权限指令
实现页面操作按钮级别的控制,通过查找父元素然后移除它下面子元素来控制按钮的显示与与否
Vue.directive('permission', {
inserted: function (el, binding) {
const { value } = binding
const actionList = store.state.user.permission
if (value) {
const hasPermission = actionList .some(btnKey => btnKey === value)
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`需要指定权限标识!如:v-permission="'editInfo'"`)
}
}
})
防抖指令
单位时间只触发最后一次
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)
}
}
})
节流指令
一段时间内首次触发时立即执行,此时间段内再次触发,不会执行
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.addEventListener(event, () => {
const nowTime = new Date().getTime()
if (!el.preTime || nowTime - el.preTime > time) {
el.preTime = nowTime
fn();
}
})
}
})
图片懒加载
监听 scroll 事件,然后动态将图片的自定义属性 data-src 的值赋予 src 来达到实际路径的目的
/*
* lazyLoad
* img v-LazyLoad="image1.jpg">
*/
Vue.directive('LazyLoad', {
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)
},
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)
},
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('draggable', {
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
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)
},
})