大家好,欢迎来到IT知识分享网。
什么是自定义指令
开发Vue项目时,大多数人都会使用到Vue内置的一些指令,例如v-model、v-if等,不过指令也是可以自定义的,为什需要自定义一个指令,其实就是想更加简洁地重复使用操作DOM的逻辑,这就和组件化和组合式函数差不多。不管是内置的指令还是自定义指令,都是类似于组件的生命周期,可以在不同的生命周期完成不同的逻辑操作,并绑定到组件元素上,这就产生了自定义指令。
自定义指令的定义方式非常简单,只需要调用Vue.directive( )方法,并传入两个参数:指令名称和指令选择对象。指令选择对象可以包含多个属性。
自定义指令的两种作用域
自定义指令可以定义全局的,也可以定义局部的。局部的自定义指令就只能在当前的.vue文件中使用,全局的则可以在所有的 .vue中使用
局部自定义指令
<script lang="ts"> import {ref} from 'vue' export default { name:'MyVue01', setup() { const a=ref(1) const btnClick = () =>{ a.value++ } return { a, btnClick } }, directives: { onceClick:{ mounted(el,binding,vnode) { el.addEventListener('click',()=>{ if (!el.disabled) { el.disabled = true setTimeout( ()=>{ el.disabled = false }, binding.value || 1000) } }) }, } } } </script> <template> <main> <button v-onceClick="1000" @click="btnClick">按钮</button> </main> </template>
自定义了一个名叫 onceClick 的指令,给一个 button 按钮加上这个指令之后,可以设置这个 button 按钮在点击多久之后,处于禁用状态,防止用户重复点击。
全局自定义指令
全局指令我们一般写在 main.js 中,或者写一个单独的 js 文件然后在 main.js 中引入,下面的例子是直接写在 main.js 中
const app = createApp(App) app.directive('onceClick', { mounted(el, binding, vnode) { el.addEventListener('click', () => { if (!el.disabled) { el.disabied = true; setTimeout(() => { el.disabled = false; }, isBindingElement.value || 10000) } }) }, })
自定义指令的生命周期
指令的生命周期和组件的生命周期类似:
//没有beforeCreated生命周期 app.directive('focus', { //在绑定元素的属性前,或者事件监听器应用前调用 created() { console.log('created'); }, //元素被插入到DOM前调用,例如我们想要实现输入框的自动聚焦,就不能在beforeMount钩子中实现 beforeMount() { console.log('beforeMount'); }, //绑定元素的父组件以及自己的所有子节点都挂载完毕后调用,这个时候DOM已经渲染出来,我们实现输入框自动聚焦也是在这个钩子函数中实现 mounted() { console.log('mounted'); }, //绑定元素的父组件更新前调用 beforeUpdate() { console.log('beforeUpdate'); }, //绑定元素的父组件以及自己的所有子节点都更新完毕后调用 updated() { console.log('updated'); }, //绑定元素的父组件卸载前调用 beforeUnmount() { console.log('beforeUnmount'); }, //绑定元素的父组件卸载后调用 unmounted() { console.log('unmounted'); } })
生命周期四个参数
- el:指令所绑定的元素,可以用来直接操作 DOM,我们松哥说想实现一个可以自动判断组件显示还是隐藏的指令,那么就可以通过 el 对象来操作 DOM 节点,进而实现组件的隐藏。
- binding:我们通过自定义指令传递的各种参数,主要存在于这个对象中,该对象属性较多,如下属性是我们日常开发使用较多的几个:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-hasPermission=”[‘user:delete’]” 中,绑定值为’user:delete’,不过需要小伙伴们注意的是,这个绑定值可以是数组也可以是普通对象,关键是看你具体绑定的是什么,在 2.1 小节的案例中,我们的 value 就是一个数字。
expression:字符串形式的指令表达式。例如 v-my-directive=”1 + 1″ 中,表达式为 “1 + 1″。
arg:传给指令的参数,可选。例如v-hasPermission:[name]=”‘zhangsan'” 中,参数为 “name”。
- vnode:Vue 编译生成的虚拟节点。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
实用的自定义指令
1. 复制粘贴指令 v-copy
实现一键复制文本内容,用于鼠标右键粘贴。
思路:①动态创建textarea标签,并设置readOnly属性及移出可视区域。②将要复制的值赋给textarea标签的value属性,并插入到body。③选中textarea移除。⑤在第一次调用时绑定事件,在解绑时移除事件
const copy = { bind(el, {value}){ el.$value = value el.handler = () => { if (!el.$value) { //值为空 控制台打印提示 console.log('无内容'); return } //动态创建 textarea 标签 const textarea = document.createElement('textarea') // 将该 textarea 设为 readonly 防止 ios 下自动唤起键盘,同时将textarea移出可视区域 textarea.readOnly='readonly' textarea.style.position='absolute' textarea.style.left='-9999px' //将要copy 的值赋给textatea标签的value属性 textarea.value = el.$value 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
使用:
<template> <div> nihao <button v-copy="copyText">复制</button> {
{ copyText }} </div> </template> <script> export default { data() { return { copyText:'vue-3' } }, } </script>
2. 长按指令 v-longpress
实现长按,用户需要按下并按住按钮几秒钟,触发响应事件
思路:①创建一个计时器,2秒后执行函数。②当用户按下按钮时触发mousedown事件,启动计时器,用户松开按钮时调用mouseout事件。③如果mouseup事件2秒内被触发,就清除计时器,当做一个普通点击事件。④如果计时器没有在2秒内清除,则判定为一次长按,可以执行关联的函数。⑤在移动端要考虑touchstart,touchend事件
const 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) }, } export default longpress
使用:
<template> <button v-longpress="longpress">长按</button> </template> <script> export default { methods: { longpress () { alert('长按指令生效') } } } </script>
输入框防抖指令 v-debounce
防止按钮在短时间内被 多次点击,使用防抖函数限制规定时间内只能点击一次
思路:① 定义一个延迟执行的方法,如果在延迟时间内在调用该方法,则重新计算执行时间。②将时间绑定在 click 方法上
const debounce = { inserted: function (el, binding) { let timer el.addEventListener('keyup', () => { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { binding.value() }, 1000) }) }, } export default debounce
使用:
<template> <button v-debounce="debounceClick">防抖</button> </template> <script> export default { methods: { debounceClick () { console.log('只触发一次') } } } </script>
禁止表情及特殊字符 v-emoji
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) } const emoji = { 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) }, } export default emoji
使用:
<template> <input type="text" v-model="note" v-emoji /> </template>
图片懒加载 v-LazyLoad
实现一个图片懒加载指令,只加载浏览器可见区域的图片。
思路:①判断当前图片是否到了可视区域这一核心逻辑实现的。②拿到所有的图片Dom遍力每个图片判断当前图片是否到了可视区范围内。③如果到了就设置图片的src属性,否则显示默认图片
图片懒加载有两种方式可以实现,一是绑定 srcoll 事件进行监听,二是使用 IntersectionObserver 判断图片是否到了可视区域,但是有浏览器兼容性问题。
下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 + 节流的方法实现。
const LazyLoad = { 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) }, 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) }) }, 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 (...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(()=>{ prevTime=Date.now() timer=null fn.apply(context,args) },delay) } } } export default LazyLoad
使用:
<img v-LazyLoad="17.png" />
权限校验指令 v-premission
自定义一个权限指令,对需要权限判断的 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) { // 没有权限 移除Dom元素 el.parentNode && el.parentNode.removeChild(el) } } }, } export default permission
使用:给 v-permission 赋值判断即可
<div class="btns"> <!-- 显示 --> <button v-permission="'1'">权限按钮1</button> <!-- 不显示 --> <button v-permission="'10'">权限按钮2</button> </div>
实现页面水印 v-waterMarker
给整个页面添加背景水印
思路:① 使用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
使用,
<template> <div v-waterMarker="{text:'lzg版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div> </template>
拖拽指令 v-draggable
实现一个拖拽指令,可在页面可视区域任意拖拽元素
思路:①设置需要拖拽的元素为相对定位,其父元素为绝对定位。②鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。③鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值④鼠标松开(onmouseup)时完成一次拖拽
const 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 } } }, } export default draggable
使用:
<template> <div class="el-dialog" v-draggable></div> </template>
总结:本人才疏学浅,水平有限,请大家多多包涵和指点。多谢🤝🫡 👍。 加油✊🫶
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/119358.html