批量自动注册全局自定义指令
- src 目录下新建目录 directives
- 在目录 directives 中创建自定义指令文件,以 v-copy 为例,则新建文件 copy.ts
- 在 main.ts 的
const app = createApp(App)
下方添加
// import.meta.glob 是 vite 提供的语法,用于导入指定目录下的所有模块
const directives = import.meta.glob('./directives/*.ts', {eager: true,import: 'default'
})import type { Directive } from 'vue'// 批量注册 directives 中的自定义指令
for (const key in directives) {app.directive(key.replace('./directives/', '').replace('.ts', ''), directives[key] as Directive)
}
v-copy 一键复制
src/directives/copy.ts
复制成功后弹出的提示部分,可根据需要自定义修改!
// v-copy="data" 默认复制成功后弹出提示
// v-copy:noInfo="data" 不显示复制成功后的提示
import type { Directive, DirectiveBinding } from 'vue'
import { useClipboard } from '@vueuse/core'interface ElType extends HTMLElement {__handleClick: () => voidcopyData?: any
}const copy: Directive = {mounted: function (el: ElType, binding: DirectiveBinding) {el.copyData = binding.valueconst { copy } = useClipboard({ source: binding.value })const { modifiers } = bindingconst handleClick = () => {copy(el.copyData)if (!modifiers.noInfo) {alert('复制成功')}}el.addEventListener('click', handleClick)el.__handleClick = handleClick},updated: function (el, binding) {el.copyData = binding.value},beforeUnmount: function (el) {el.removeEventListener('click', el.__handleClick)}
}export default copy
页面使用
<script setup lang="ts">
let content = '被复制的内容'
</script><template><div class="p4"><div>{{ content }}</div><button v-copy="content">复制(弹出提示复制成功)</button><button v-copy.noInfo="content">复制(禁止弹出提示复制成功)</button></div>
</template>
v-debounce 防抖
src/directives/debounce.ts
import type { Directive, DirectiveBinding } from 'vue'interface ElType extends HTMLElement {__handleClick: () => void
}const debounce: Directive = {mounted: function (el: ElType, binding: DirectiveBinding) {if (typeof binding.value !== 'function') {throw '绑定的值必须是一个函数'}let timer: number | null = nullel.__handleClick = function () {if (timer) {clearInterval(timer)}timer = window.setTimeout(() => {binding.value()}, 500)}el.addEventListener('click', el.__handleClick)},beforeUnmount: function (el) {el.removeEventListener('click', el.__handleClick)}
}export default debounce
页面使用
<button v-debounce="() => console.log('短时间连续点击,只会触发一次!')">防抖</button>
v-throttle 节流
src/directives/throttle.ts
import type { Directive, DirectiveBinding } from 'vue'interface ElType extends HTMLElement {__handleClick: () => voiddisabled?: boolean
}const throttle: Directive = {mounted: function (el: ElType, binding: DirectiveBinding) {if (typeof binding.value !== 'function') {throw '绑定的值必须是一个函数'}let timer: number | null = nullel.__handleClick = function () {if (timer) {clearInterval(timer)}if (!el.disabled) {el.disabled = truebinding.value()timer = window.setTimeout(() => {el.disabled = false}, 1000)}}el.addEventListener('click', el.__handleClick)},beforeUnmount: function (el) {el.removeEventListener('click', el.__handleClick)}
}export default throttle
页面使用
<button v-throttle="() => console.log('点击后会立马禁止点击,1秒后才能再次点击!')">节流</button>
v-longPress 长按
src/directives/longPress.ts
import type { Directive, DirectiveBinding } from 'vue'interface ElType extends HTMLElement {_unmounted?: () => void
}const mounted = (el: ElType, binding: DirectiveBinding) => {if (typeof binding.value !== 'function') {throw 'callback must be a function'}// 定义变量let pressTimer: any = null// 创建计时器( 2秒后执行函数 )const start = (e: any) => {if (e.type === 'mousedown' && 'button' in e && e.button !== 0) {return}if (pressTimer === null) {pressTimer = setTimeout(() => {handler(e)}, 2000)}}// 取消计时器const cancel = () => {if (pressTimer !== null) {clearTimeout(pressTimer)pressTimer = null}}// 运行函数const handler = (e: MouseEvent | TouchEvent) => {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)// 移除事件监听器const unmounted = () => {el.removeEventListener('mousedown', start)el.removeEventListener('touchstart', start)el.removeEventListener('click', cancel)el.removeEventListener('mouseout', cancel)el.removeEventListener('touchend', cancel)el.removeEventListener('touchcancel', cancel)}el._unmounted = unmounted
}const longPress: Directive = {mounted,beforeUnmount: (el) => el?._unmounted()
}export default longPress
页面使用
<button v-longPress="() => console.log('长按2秒后触发!')">长按</button>
v-waterMarker 水印
src/directives/waterMarker.ts
/*给整个页面添加背景水印使用:设置水印文案,颜色,字体大小<div v-waterMarker="{text:'版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
*/import type { Directive, DirectiveBinding } from 'vue'const addWaterMarker: Directive = (str: string, parentNode: any, font: any, textColor: string) => {// 水印文字,父元素,字体,文字颜色const can: HTMLCanvasElement = document.createElement('canvas')parentNode.appendChild(can)can.width = 200can.height = 150can.style.display = 'none'const cans = can.getContext('2d') as CanvasRenderingContext2Dcans.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' as CanvasTextBaselinecans.fillText(str, can.width / 10, can.height / 2)parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
}
const mounted = (el: DirectiveBinding, binding: DirectiveBinding) => {addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
}const waterMarker: Directive = {mounted
}export default waterMarker
页面使用
<div class="p4 h-full" v-waterMarker="{ text: '绝密', textColor: 'rgba(180, 180, 180, 0.4)' }"><p>版权所有</p><p>商业机密</p><p>严禁泄密</p><p>哈哈哈哈</p></div>
效果如下
v-draggable 拖拽
src/directives/draggable.ts
/*在父元素区域任意拖拽元素。思路:1、设置需要拖拽的元素为absolute,其父元素为relative。2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值4、鼠标松开(onmouseup)时完成一次拖拽使用:在 Dom 上加上 v-draggable 即可<div v-draggable></div>
*/import type { Directive } from 'vue'
interface ElType extends HTMLElement {parentNode: any
}const mounted = (drag_dom: ElType) => {drag_dom.style.cursor = 'move'drag_dom.style.position = 'absolute'drag_dom.style.userSelect = 'none'drag_dom.onmousedown = function (e) {// 按下鼠标时,鼠标相对于被拖拽元素的坐标const disX = e.clientX - drag_dom.offsetLeftconst disY = e.clientY - drag_dom.offsetTop// 获取容器domconst container_dom = drag_dom.parentNodedocument.onmousemove = function (e) {// 用鼠标的位置减去鼠标相对元素的位置,得到元素的位置const left = e.clientX - disXconst top = e.clientY - disY// 在容器范围内移动时,被拖拽元素的最大left值const leftMax = container_dom.offsetLeft + (container_dom.clientWidth - drag_dom.clientWidth)// 在容器范围内移动时,被拖拽元素的最小left值const leftMin = container_dom.offsetLeftif (left > leftMax) {drag_dom.style.left = leftMax + 'px'} else if (left < leftMin) {drag_dom.style.left = leftMin + 'px'} else {drag_dom.style.left = left + 'px'}// 在容器范围内移动时,被拖拽元素的最大left值const topMax = container_dom.offsetTop + (container_dom.clientHeight - drag_dom.clientHeight)// 在容器范围内移动时,被拖拽元素的最小left值const topMin = container_dom.offsetTopif (top > topMax) {drag_dom.style.top = topMax + 'px'} else if (top < topMin) {drag_dom.style.top = topMin + 'px'} else {drag_dom.style.top = top + 'px'}}document.onmouseup = function () {document.onmousemove = document.onmouseup = null}}
}const draggable: Directive = {mounted
}export default draggable
页面使用
<div class="border h-200px w-400px m-10"><div class="bg-red" v-draggable>元素</div></div>
v-typewriter 打字机特效
src/directives/typewriter.ts
import type { Directive } from 'vue'const blinkAnimationStyle = `
@keyframes blink {to {visibility: hidden}
}.cursor1:after {-webkit-animation: blink 1s steps(5, start) infinite;animation: blink 1s steps(5, start) infinite;content: "_";margin-left: .25rem;vertical-align: baseline
}
`function addStylesOnce(css: string, id: string) {if (!document.head.querySelector(`#${id}`)) {const style = document.createElement('style')style.id = idstyle.appendChild(document.createTextNode(css))document.head.appendChild(style)}
}const timerMap = new WeakMap<Element, number[]>()function clearTimers(el: Element) {const timers = timerMap.get(el)if (timers) {timers.forEach(clearInterval)timerMap.delete(el)}
}// 一段文字
const typewriter: Directive = {mounted: function (el: HTMLElement) {// css -> 模拟光标addStylesOnce(blinkAnimationStyle, 'v-typewriter-animation')// js -> DOM文字的显示 -> 计时器来进行定时执行// 1.拿到DOM中的文字text -> 总长度const text = el.innerTextconst children = el.childrenconst run = (elem: Element, txt: string, cb?: () => void) => {clearTimers(elem)elem.classList.add('cursor1')// 2.设置一个随机数random,长度小于上面的文本长度const len = txt.lengthconst random = Math.floor(Math.random() * len)let content = txt// 3.删除text中random个字符,每隔200ms删除一个,直到删除random个const timer1 = setInterval(() => {content = content.substring(0, content!.length - 1)elem.textContent = contentif (content!.length === random) {// 4.删除完成之后,每隔0.5s添加一个字符,直到添加完毕,这个是一个轮回。const timer2 = setInterval(() => {content = content + txt!.charAt(content!.length)elem.textContent = contentif (content.length === txt.length) {clearInterval(timer2)elem.classList.remove('cursor1')setTimeout(() => {if (typeof cb === 'function') {cb()} else {run(elem, txt)}}, 5000)}}, 500)clearInterval(timer1)timerMap.get(elem)?.push(timer2 as unknown as number)}}, 200)timerMap.get(elem)?.push(timer1 as unknown as number)}const runChildren = () => {// children长度,随机一个数// 对随机的child执行run方法const random = Math.floor(Math.random() * children.length)const node = children[random]const textNode = node.textContentif (textNode) {run(node, textNode, runChildren)}}if (text && children.length === 0) {run(el, text)} else {runChildren()}},beforeUnmount: function (el) {clearTimers(el)}
}export default typewriter
页面使用
<p v-typewriter>熟读唐诗三百首,不会作诗也会吟。</p><ul v-typewriter><li>1. 天下兴亡,匹夫有责。——顾炎武</li><li>2. 人生自古谁无死,留取丹心照汗青。——文天祥</li></ul>