赞
踩
在vue官方文档中是这样描述的,自定义指令主要是为了重用涉及普通元素的底层DOM访问的逻辑。其实了解到自定义指令的作用在于,在某些场景下需要对普通DOM元素进行操作就行。
Vue 2.X 与 Vue 3.X 相比,钩子函数是存在变化的
指令的钩子会传递以下几种参数:
自定义指令要分全局自定义指令和局部指令
全局指令:通过应用实例身上的directive()注册一个全局自定义指令
Vue.directive(指令名, { 自定义指令生命周期 })
// Vue 2.X import Vue from 'vue' Vue.directive('focus', { inserted: function(el) { el.focus() } }) // Vue 3.X const app = createApp({}) app.directive('focus', { mounted(el) { el.focus() } })
这是一个简单的小案例,通过注册一个v-focus指令,实现一个在页面加载完成后自动让输入框获取焦点的小功能。
可在组件中配置directives选项来注册局部指令
directives(指令名, { 自定义指令生命周期 })
// Vue 2.X directives: { focus: { inserted: function(el) { el.focus() } } } // Vue 3.X directives: { focus: { mounted(el) { el.focus() } } }
批量注册是为了方便注册更多的自定义指令
import copy from "./copy";
import longpress from "./longpress";
const directives = {
copy,
longpress,
};
export default {
install(Vue) {
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key]);
});
},
};
// Vue 2.X
import Vue from 'vue'
import Directive from './directives'
Vue.use(Directive)
/ Vue 3.X
import { createApp } from 'vue'
import App from './App.vue'
import Directive from './directives'
const app = createApp(App)
app.use(Directive)
app.mount('#app')
实现: 一键复制文本内容,用于粘贴
思路:
- 动态创建textarea标签,并设置reaOnly属性及移除可视区域
- 将要复制的值赋给textarea标签的value属性,并插入到body
- 选中值textarea并复制
- 将body中插入的textarea移除
- 在第一次调用时绑定事件,在解绑时移除事件
const copy = { bind(el, { value }) { el._value = value el.handler = () => { if (!el._value) { // 复制的值为空时,给出的提示操作。 console.log('无可复制的内容') return } // 创建 textarea 标签 const textarea = document.createElement('textarea') // 设置readOnly(规定字段为只读)属性,防止 iOS下自动唤起键盘,并移除可视区域 textarea.readOnly = 'readOnly' textarea.style.position = 'absolute' textarea.style.left = '-9999px' // 将需要复制的值,赋给 textarea 标签的 value 值 textarea.value = el._value // 将 textarea 插入到 body 中 document.body.appendChild(textarea) // 选中值并复制 textarea.select() const result = document.execCommand('Copy') if (result) { console.log('复制成功') } document.body.removeChild(textarea) } // 绑定点击事件 el.addEventListener('click', el.handler) }, // 当传进来的值更新的时候触发 componentUpdated(el, { value }) { el._value = value }, // 指令与元素解绑的时候触发,移除事件绑定 unbind(el) { el.removeEventListener('click', el.handler) } } export default copy
使用:
给DOM节点加上v-copy及复制文本即可
<template>
<button v-copy="text">复制</button>
</template>
<script>
export default {
data () {
return {
text: '这是一条复制文本'
}
}
}
</script>
实现:长按超过两秒,执行回调函数
思路:
- 创建一个计时器,两秒后执行函数
- 当用户按下按钮时触发mousedown事件(移动端touchstart事件),启动计时器;
- 用户松开按钮时调用mouseout事件(移动端touchend事件)
- 如果mouseup事件在两秒内触发,此事件当作普通点击事件
- 如果计时器没有在两秒内清除,定为长按事件,触发相关回调函数
const longpress = { bind: (el, binding, vNode) { // 没有绑定函数抛出错误 if (typeof binding.value !== 'function') { throw 'longpress callback not a function' } // 计时器变量 el._timer = null // 运行函数 el._handler = e => { binding.value(e) } // 创建计时器(2秒后执行函数) el._start = e => { // 0为鼠标左键 if (e.type === 'click' && e.button !== 0) return if (el._timer === null) { el._timer = setTimeout(_ => { el._handler() }, 2000) // 取消浏览器默认事件 el.addEventListener('contextmenu', 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="handleLongpress">复制</button>
</template>
<script>
export default {
methods: {
handleLongpress() {
alert('这是长按指令')
}
}
}
</script>
场景:项目开发中,经常会遇到按钮多次点击后,重复请求接口,造成数据混乱,例如表单提交。
实现:使用防抖,防止按钮在短时间内多次点击,设置按钮在1秒内只能点击一次。
思路:
- 定义延时方法,在1秒内再调用该方法,则重新计算执行时间
- 将事件绑定在点击方法上
const debounce = { inserted: (el, binding) => { // 没有绑定函数抛出错误 if (typeof binding.value !== 'function') { throw 'debounce callback not a function' } let timer el.addEventListener('click', () => { if (timer) clearTimeout(timer) timer = setTimeout(_ => { binding.value() }, 1000) }) } } export default debounce
使用:
给DOM节点加上v-debounce及回调函数
<template>
<button v-debounce="handleDebounce">防抖</button>
</template>
<script>
export default {
methods: {
handleDebounce() {
console.log('防抖,触发一次')
}
}
}
</script>
场景:同防抖指令场景
实现:使用节流方法,限制1秒内按钮只能点击一次
思路:
- 定义一个开关,默认时打开状态
- 在点击按钮后关闭开关,在1秒内再点击按钮,不执行回调函数
- 1秒过后,开关打开
const throttle = { bind: (el, binding) => { // 没有绑定函数抛出错误 if (typeof binding.value !== 'function') { throw 'throttle callback not a function' } // 开关 el._flag = true el._timer = null el._handler = () => { if (!el._flag) return // 函数执行后关闭开关 el._flag && binding.value() el._flag = false if (el._timer) clearTimeout(el._timer) el._timer = setTimeout(_ => { el._flag = true }, 1000) } el.addEventListener('click', el._handler) }, unbind: (el, binding) => { el.removeEventListener('click', el._handler) } } export default throttle
使用:
给DOM节点加上v-throttle及回调函数
<template>
<button v-throttle="handleThrottle">节流</button>
</template>
<script>
export default {
methods: {
handleThrottle() {
console.log('节流,触发一次')
}
}
}
</script>
场景:弹窗,点击弹窗外部(即蒙版),关闭弹窗
实现:点击指定区域外部,执行相应回调函数
思路:
- 判断点击的元素是否指定元素
- 是则不执行,否则执行相应回调函数
const clickOut = { bind: (el, binding) => { el._handler = e => { // 判断点击的元素是否本身,是则返回 if (el.contains(e.target)) return // 如果绑定了函数,则调用函数 if (typeof binding.value === 'function') { binding.value() } } // 添加事件监听 setTimeout(_ => { document.addEventListener('click', el._handler) }, 0) }, unbind(el) { // 解除事件监听 document.removeEventListener('click', el._handler) } } export default clickOut
使用:
给指定DOM节点加v-clickOut及回调函数
<template>
<!-- v-clickOut、v-click-out都可以使用 -->
<div style="width:100px;height:100px;background-color:red;" v-click-out="handleClickOut">target DOM</div>
</template>
<script>
export default {
methods: {
handleClickOut() {
console.log('外部元素触发')
}
}
}
</script>
实现:指定元素在可视区域内任意拖拽
思路:
- 设置指定元素相对定位,父元素绝对定位
- 鼠标按下时(触发onmousedown事件)时记录指定元素当前的left、top值
- 鼠标移动时(触发onmousemove事件)时计算每次移动的横向距离、纵向距离,并改变left、top值
- 鼠标松开时(触发onmouseup事件)时完成一次拖拽
const draggable = { inserted: (el) => { el.style.cursor = 'move' el._onmousedown = e => { // 记录当前的left、top值 let disX = e.pageX - el.offsetLeft let disY = e.pageY - el.offsetTop // 鼠标移动 document.onmousemove = 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 = _ => { document.onmousemove = document.onmouseup = null } } } } export default draggable
使用:
给指定DOM节点加v-draggable即可
<template>
<!-- 需要加上position:absolute -->
<div style="width:40px;height:40px;background-color:green;position:absolute;" v-draggable></div>
</template>
实现:根据正则表达式,设计自定义处理表单输入规则的指令
思路:
- 定位input输入框(el-input输入框外会包裹一层div)
- 监听输入事件,对只保留整数或保留小数分别处理
- 通过正则表达式对保留小数做处理
- 将匹配后的值赋值给输入框
const inputNumber = { bind: (el, binding, vnode) => { // 定位输入框 let input = el.tagName === 'INPUT' ? el : vnode.elm.children[0] // compositionstart -> 开始新的输入合成时会触发 input.addEventListener('compositionstart', _ => { vnode.inputLocaking = true }) // compostitonend -> 合成完成或取消时触发 input.addEventListener('compostitonend', _ => { vnode.inputLocaking = false input.dispatchEvent(new Event('input')) }) input.addEventListener('input', _ => { if (vnode.inputLocaking) return let oldValue = input.value let newValue = input.value if (binding.modifiers.float) { // 清除数字和‘.’以外的字符 newValue = newValue.replace(/[^\d./g, '') // 只保留第一个‘.’,清除多余的 newValue = newValue.replace(/\.{2,}/g, '.') // 第一个字符如果是‘.’,补充前缀0 newValue = newValue.replace(/^\./g, '0.') // 0开头的只有保留第一个0,清除多余的 newValue = newValue.replace(/^0{2,}/g, '0') // 两位数以上不能0开头 if (/^0\d+/.test(newValue)) { newValue = newValue.slice(1) } // 保证‘.’只出现一次,而不能出现两次以上 newValue = newValue.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.') // 保留几位小数 if (typeof binding.value !== 'undefined') { // 期望保留的最大小数位数 let poinKeep = 0 if (typeof binding.value === 'string' || typeof binding.value === 'number') { pointKeep = parseInt(binding.value) } if (!isNaN(pointKeep)) { if (!Number.isInteger(pointKeep) || pointKeep < 0) { pointKeep = 0 } const str = '^(\\d+)\\.(\\d{'+pointKeep+'}).*$' const reg = new RegExp(str) if (pointKeep === 0) { // 不需要小数点 newValue = newValue.replace(reg, '$1') } else { // 通过正则表达式保留小数点后指定的位数 newValue = newValue.replace(reg, '$1.$2') } } } } else { // 只保留整数 newValue = newValue.replace(/[^\d]/g, '') newValue = newValue ? parseInt(newValue) : '' } // 判断是否需要更新,避免进入死循环 if (+newValue !== +oldValue) { input.value = newValue input.dispatchEvent(new Event('input')) } }) } } export default inputNumber
使用:
整数直接给输入框加v-inputNumber,带小数的加上参数v-inputNumber.float=“2”
<template> <div class="input-wrapper"> <el-input v-model="value1" placeholder="整数" v-inputNumber></el-input> <el-input v-model="value2" placeholder="保留两位小数" v-inputNumber.float="2"></el-input> </div> </template> <script> export default { data () { return { value1: '', value2: '' } } } </script>
实现:给指定页面添加背景水印
思路:
- 使用canvas特性生成base64格式的图片文件,设置其字体大小,颜色等。
- 将其设置为背景图,实现水印效果
function 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') + ')' } const waterMarker = { bind: function (el, binding) { addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor) } } export default waterMarker
使用:
给DOM节点加上v-waterMarker,并设置水印文案,字体大小及颜色等
<template> <div class="water-marker" v-waterMarker="waterMarker"></div> </template> <script> export default { waterMarker: { text: 'Fuleny版权所有', font: '14px Microsoft JhengHei', textColor: 'rgba(180, 180, 180, 0.4)' } } </script> <style lang="scss" scoped> .water-marker { width: 400px; height: 400px; border: 1px solid #eee; } </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。