vue项目中如果有自定义指令会更加舒适
全局自定义指令
节流
v-preventReClick='2000' // 2秒控制
import Vue from 'vue';
Vue.directive('preventReClick', {
inserted (el, binding) {
el.addEventListener('click', () => { //dom调用click触发
if (!el.disabled) { //没有禁用的时候进入
el.disabled = true; //变成禁用
el.style.cursor = 'default'; // 改变手势
setTimeout(() => {
el.disabled = false; //改成不禁用
el.style.cursor = 'pointer';
}, binding.value || 2000); //可以自定义节流的时间
}
});
}
});
input输入限制数字大小
v-positiveInt="{min:1,max:1000}"
const trigger = (el, type) => {
const e = document.createEvent('HTMLEvents');
e.initEvent(type, true, true);
el.dispatchEvent(e);
};
// 针对 el-input做的限制,只能输入正整数
Vue.directive('positiveInt', {
bind(el, binding) {
const { min, max } = _.get(binding, 'value', {});
const input = el.getElementsByTagName('input')[0];
input.onkeyup = function () {
if (input.value.length === 1) {
input.value = input.value.replace(/[^0-9]/g, '');
} else {
input.value = input.value.replace(/[^\d]/g, '');
}
if (min && input.value && Number(input.value) < Number(min)) {
input.value = min;
}
if (max && Number(input.value) > Number(max)) {
input.value = max;
}
trigger(input, 'input');
};
input.onblur = function () {
if (input.value.length === 1) {
input.value = input.value.replace(/[^0-9]/g, '');
} else {
input.value = input.value.replace(/[^\d]/g, '');
}
if (min && input.value && Number(input.value) < Number(min)) {
input.value = min;
}
if (max && Number(input.value) > Number(max)) {
input.value = max;
}
trigger(input, 'input');
};
}
});
input输入限制
v-input-limit='0' && v-input-limit
- 加了后input只能输入整数
v-input-limit='2' // 加了后input 可以输入两位小数
Vue.directive('input-limit', {
bind(el, binding) { // 定义dom节点和值
binding.value = binding.value || 0 // 如果有value 就使用value 如果没有 就用0
const wins_0 = /[^\d]/g // 整数判断
const wins_1 = /[^\d^\.]/g // 小数判断
let flag = true;
let points = 0;
let lengths = 0
let remainder = 0
let no_int = 0
//如果el是一个节点就返回节点 如果不是就注册input事件 el-input是一个组件不是节点
const target = el instanceof HTMLInputElement ? el : el.querySelector('input');
// 但是在 iOS 中,input 事件会截断非直接输入,什么是非直接输入呢,在我们输入汉字的时候,比如说「喜茶」,
// 中间过程中会输入拼音,每次输入一个字母都会触发 input 事件,然而在没有点选候选字或者点击「选定」按钮前,都属于非直接输入。
// 这显然不是我们想要的结果,我们希望在直接输入之后才触发 input 事件,
// ================================================================================================
// 这就需要引出我要说的两个事件—— compositionstart 和compositionend。
// compositionstart 事件在用户开始进行非直接输入的时候触发,而在非直接输入结束,
// 也即用户点选候选词或者点击「选定」按钮之后,会触发 compositionend 事件。
// ================================================================================================
// 添加一个 flag 变量,当用户未完成直接输入前,flag 为 true,
// 不触发 input 事件中的逻辑,当用户完成有效输入之后,inputLock 设置为 false,触发 input 事件的逻辑。
// 这里需要注意的一点是,compositionend 事件是在 input 事件后触发的,所以在 compositionend事件触发时,也要调用 input 事件处理逻辑。
target.addEventListener('compositionstart', e => { //未输入完 还在候选词的时候触发
flag = false;
});
target.addEventListener('compositionend', e => { //输入完 结束候选词的时候触发
flag = true;
});
target.addEventListener('input', e => { // 给input监听事件
setTimeout(function() {
if (flag) { //如果输入结束了 就触发
if (binding.value === 0) { // 如果值等于0
if (wins_0.test(e.target.value)) {
e.target.value = e.target.value.replace(wins_0, '');
e.target.dispatchEvent(new Event('input')) // 手动更新v-model值
}
}
if (binding.value === 1) {
if (wins_0.test(e.target.value.toString().replace(/\d+\.(\d*)/, '$1'))) {
remainder = true
}
if ((e.target.value.split('.')).length - 1 > 1) {
points = true
}
if (e.target.value.toString().split('.')[1] !== undefined) {
if (e.target.value.toString().split('.')[1].length > 1) {
lengths = true
}
}
if (e.target.value.toString().indexOf('.') !== -1) {
no_int = false
} else {
no_int = true
}
if (wins_1.test(e.target.value) || lengths || points || remainder) {
if (!no_int) {
e.target.value = e.target.value.replace(wins_1, '').replace('.', '$#$').replace(/\./g, '').replace(
'$#$', '.').replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3').substring(0, e.target.value.indexOf(
'.') + 2)
} else {
e.target.value = e.target.value.replace(wins_0, '')
}
e.target.dispatchEvent(new Event('input'))
}
}
if (binding.value === 2) {
if (wins_0.test(e.target.value.toString().replace(/\d+\.(\d*)/, '$1'))) {
remainder = true
}
if ((e.target.value.split('.')).length - 1 > 1) {
points = true
}
if (e.target.value.toString().split('.')[1] !== undefined) {
if (e.target.value.toString().split('.')[1].length > 2) {
lengths = true
}
}
if (e.target.value.toString().indexOf('.') !== -1) {
no_int = false
} else {
no_int = true
}
if (wins_1.test(e.target.value) || lengths || points || remainder) {
if (!no_int) {
e.target.value = e.target.value.replace(wins_1, '').replace('.', '$#$').replace(/\./g, '').replace(
'$#$', '.').replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3').substring(0, e.target.value.indexOf(
'.') + 3)
} else {
e.target.value = e.target.value.replace(wins_0, '')
}
e.target.dispatchEvent(new Event('input'))
}
}
}
target.dispatchEvent(new Event('input')) // 这个是点睛之笔
}, 0)
})
}
})
权限控制自定义指令
v-permission
- 我们肯需要更加用户角色进行一些操作权限的判断 很多时候我们都是给一个元素添加v-if来进行显示隐藏
- 但是判断条件繁琐就不太好
- 需求: 自定义一个权限指令 对需要权限判断的dom进行显示隐藏
- 判断用户的权限是否在这个数组内 如果是则显示 否则移出dom
function checkArray(key) {
let arr = ['1', '2', '3', '4'] // 把权限的名字写在这里面
let index = arr.indexOf(key)
if (index > -1) {
return true // 有权限
} else {
return false // 无权限
}
}
const permission = {
inserted: function (el, binding) {
let permission = binding.value // 获取到 v-permission的值
if (permission) {
let hasPermission = checkArray(permission) // 把输入的值传到方法里去判断
if (!hasPermission) { // 如果这个方法返回的false 说明没有权限
el.parentNode && el.parentNode.removeChild(el) // 没有权限 移除Dom元素
}
}
},
}
export default permission
使用:
给v-permission 判断赋值即可
<div class="btns">
<button v-permission="'1'">权限按钮1</button> // <!-- 显示 -->
<button v-permission="'10'">权限按钮2</button> // <!-- 权限数组里没有10所以不显示 -->
</div>
图片懒加载自定义指令
v-LazyLoad
- 背景:在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。
- 需求:实现一个图片懒加载指令,只加载浏览器可见区域的图片。
- 图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的 拿到所有的图片 Dom ,遍历每个图片判断当前图片是否到了可视区范围内
- 如果到了就设置图片的 src 属性,否则显示默认图片
图片懒加载有两种方式可以实现,一是绑定 srcoll 事件进行监听,二是使用 IntersectionObserver 判断图片是否到了可视区域,但是有浏览器兼容性问题。 - 下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 + 节流的方法实现。
const LazyLoad = {
// install方法
install(Vue, options) {
const defaultSrc = 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监听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)
})
},
// 加载真实图片
load(el) {
const windowHeight = document.documentElement.clientHeight
const elTop = el.getBoundingClientRect().top
const elBtm = el.getBoundingClientRect().bottom
const realSrc = el.dataset.src
if (elTop - windowHeight < 0 && elBtm > 0) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
},
// 节流
throttle(fn, delay) {
let timer
let prevTime
return function (...args) {
const currTime = Date.now()
const context = this
if (!prevTime) prevTime = currTime
clearTimeout(timer)
if (currTime - prevTime > delay) {
prevTime = currTime
fn.apply(context, args)
clearTimeout(timer)
return
}
timer = setTimeout(function () {
prevTime = Date.now()
timer = null
fn.apply(context, args)
}, delay)
}
},
}
- export default LazyLoad
- 使用方法
- 将组件内 标签的 src 换成 v-LazyLoad
<img v-LazyLoad="xxx.jpg" />