日常记录,随时补充
1.指令解释
允许封装 DOM 操作逻辑并在模板中复用,用于专注于处理 DOM 的底层操作,例如元素聚焦、权限控制、事件节流等。
2.如何写一个自定义指令
1.全局注册(在 main.js 中)
在应用入口文件(通常是 main.js
)中使用 app.directive()
方法全局注册
javascript
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 全局注册 v-focus 指令
app.directive('focus', {
// 当被绑定的元素插入到 DOM 中时...
mounted(el) {
// 聚焦元素
el.focus()
}
})
app.mount('#app')
多个指令在src下面新建directives文件夹,新建指令,在main.js中导入并注册。
javascript
// src/directives/permission.js
export const permission = {
mounted(el, binding) {
}
}
// 在 src/directives/index.js 中统一导出所有指令
import { permission } from './permission'
import { debounce } from './debounce' // 其他指令...
import { throttle } from './throttle'
export default {
install(app) {
app.directive('permission', permission)
app.directive('debounce', debounce)
app.directive('throttle', throttle)
// 可继续添加其他指令...
}
}
// 在 main.js 中导入并注册指令插件
import { createApp } from 'vue'
import App from './App.vue'
import directives from './directives' // 导入指令插件
const app = createApp(App)
// 注册全局指令
app.use(directives)
app.mount('#app')
2. 局部注册(在组件内)
在组件选项中通过 directives
选项局部注册
javascript
export default {
directives: {
focus: {
mounted(el) {
el.focus()
}
}
}
}
3.模板中调用
javascript
<template>
<input v-focus />
</template>
3.指令钩子函数
javascript
app.directive('my-directive', {
// 在绑定元素的 attribute 前或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {},
// 在元素被插入到父 DOM 时调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件挂载后调用
mounted(el, binding, vnode, prevVnode) {},
// 在包含组件的 VNode 更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在包含组件的 VNode 及其子 VNode 更新后调用
updated(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
})
4.常见自定义指令
1. 自动聚焦指令
javascript
app.directive('focus', {
mounted(el) {
el.focus()
}
})
//使用
<template>
<input v-focus />
</template>
2. 权限控制指令
javascript
app.directive('permission', {
mounted(el, binding) {
const { value } = binding
const hasPermission = checkUserPermission(value) // 检查用户权限的函数
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
})
// 使用
<template>
<button v-permission="'admin'">管理按钮</button>
</template>
3. 防抖指令
javascript
app.directive('debounce', {
mounted(el, binding) {
const { value, arg = 300 } = binding
let timer = null
el.addEventListener('click', () => {
clearTimeout(timer)
timer = setTimeout(() => {
value()
}, parseInt(arg))
})
}
})
//使用
<template>
<button v-debounce="handleClick">防抖按钮</button>
</template>
4. 点击外部指令
javascript
app.directive('click-outside', {
mounted(el, binding) {
const handler = (e) => {
if (!el.contains(e.target)) {
binding.value()
}
}
document.addEventListener('click', handler)
el._clickOutsideHandler = handler
},
unmounted(el) {
document.removeEventListener('click', el._clickOutsideHandler)
}
})
// 使用
<template>
<div v-click-outside="closeMenu">
<button>菜单</button>
<div v-show="isOpen">菜单内容</div>
</div>
</template>
5. 图片懒加载指令
javascript
app.directive('lazyload', {
mounted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value
observer.unobserve(el)
}
})
})
observer.observe(el)
}
})
//使用
<template>
<img v-lazyload="imageUrl" alt="懒加载图片" />
</template>
6. 限制输入为数字
javascript
app.directive('number', {
mounted(el) {
el.addEventListener('input', (e) => {
const value = e.target.value.replace(/[^0-9]/g, '')
e.target.value = value
})
}
})
//使用
<input v-number type="text" placeholder="只能输入数字">
7. 元素拖拽
javascript
app.directive('drag', {
mounted(el) {
let isDragging = false
let offsetX, offsetY
el.style.cursor = 'move'
el.style.position = 'relative'
el.addEventListener('mousedown', (e) => {
isDragging = true
offsetX = e.clientX - el.getBoundingClientRect().left
offsetY = e.clientY - el.getBoundingClientRect().top
})
document.addEventListener('mousemove', (e) => {
if (!isDragging) return
const x = e.clientX - offsetX
const y = e.clientY - offsetY
el.style.left = `${x}px`
el.style.top = `${y}px`
})
document.addEventListener('mouseup', () => {
isDragging = false
})
}
})
// 使用
<div v-drag class="draggable-box">可拖拽元素</div>
8. 元素淡入
javascript
app.directive('fade', {
mounted(el, { value = 300 }) {
el.style.opacity = '0'
el.style.transition = `opacity ${value}ms`
setTimeout(() => {
el.style.opacity = '1'
}, 10)
}
})
// 使用
<div v-fade>淡入元素</div>
<div v-fade="1000">1秒淡入元素</div>
9. 格式化数据显示
javascript
app.directive('format', {
mounted(el, { value, arg }) {
if (arg === 'currency') {
el.textContent = `¥${Number(value).toFixed(2)}`
} else if (arg === 'date') {
el.textContent = new Date(value).toLocaleDateString()
}
}
})
// 使用
<span v-format:currency="1234.56">1234.56</span> <!-- ¥1234.56 -->
<span v-format:date="timestamp">时间戳</span> <!-- 2023/1/1 -->
10. 无限滚动加载
javascript
app.directive('infinite-scroll', {
mounted(el, { value }) {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
value()
}
},
{ threshold: 0.1 }
)
observer.observe(el)
}
})
// 使用
<div v-infinite-scroll="loadMore">加载更多</div>
11. 页面添加水印
javascript
app.directive('watermark', {
mounted(el, { value }) {
const watermark = document.createElement('div')
watermark.className = 'watermark'
watermark.textContent = value
el.appendChild(watermark)
}
})
// 使用
<div v-watermark="'机密文件'">内容区域</div>
12. 根据屏幕尺寸隐藏元素
javascript
app.directive('responsive', {
mounted(el, { value }) {
const updateVisibility = () => {
if (window.innerWidth < value) {
el.style.display = 'none'
} else {
el.style.display = ''
}
}
window.addEventListener('resize', updateVisibility)
updateVisibility() // 初始调用
}
})
// 使用
<div v-responsive="768">仅在桌面显示</div>
13. 一键复制文本
javascript
app.directive('copy', {
mounted(el, { value }) {
el.addEventListener('click', () => {
navigator.clipboard.writeText(value).then(() => {
el.textContent = '已复制'
setTimeout(() => {
el.textContent = '复制'
}, 2000)
})
})
}
})
// 使用
<button v-copy="textToCopy">复制</button>
14. 错误时抖动元素
javascript
app.directive('shake', {
updated(el, { value }) {
if (value) {
el.classList.add('shake')
setTimeout(() => {
el.classList.remove('shake')
}, 500)
}
}
})
//使用
<input v-shake="hasError" type="text">
15. 鼠标悬停缩放元素
javascript
app.directive('zoom', {
mounted(el, { value = 1.1 }) {
el.style.transition = 'transform 0.3s'
el.addEventListener('mouseenter', () => {
el.style.transform = `scale(${value})`
})
el.addEventListener('mouseleave', () => {
el.style.transform = 'scale(1)'
})
}
})
//使用
<img v-zoom src="image.jpg" alt="悬停放大">
16. 输入自动保存
javascript
app.directive('autosave', {
mounted(el, { value }) {
el.addEventListener('input', (e) => {
localStorage.setItem(value, e.target.value)
})
// 恢复已保存的值
const savedValue = localStorage.getItem(value)
if (savedValue) {
el.value = savedValue
}
}
})
// 使用
<textarea v-autosave="'form-content'"></textarea>
17. 滚动到视图时显示动画
javascript
app.directive('scroll-reveal', {
mounted(el) {
el.style.opacity = '0'
el.style.transform = 'translateY(30px)'
el.style.transition = 'opacity 0.6s ease, transform 0.6s ease'
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.style.opacity = '1'
el.style.transform = 'translateY(0)'
observer.unobserve(el)
}
})
}, { threshold: 0.1 })
observer.observe(el)
}
})
// 使用
<div v-scroll-reveal>滚动显示的内容</div>
18. 滚动时固定元素
javascript
app.directive('sticky', {
mounted(el, { value = 0 }) {
const stickyTop = value
let originalPosition = null
let originalTop = null
const handleScroll = () => {
if (window.scrollY > stickyTop) {
if (!originalPosition) {
originalPosition = el.style.position
originalTop = el.style.top
el.style.position = 'fixed'
el.style.top = '0'
}
} else {
if (originalPosition) {
el.style.position = originalPosition
el.style.top = originalTop
originalPosition = null
originalTop = null
}
}
}
window.addEventListener('scroll', handleScroll)
el._stickyHandler = handleScroll
},
unmounted(el) {
window.removeEventListener('scroll', el._stickyHandler)
}
})
// 使用
<header v-sticky>滚动时固定的头部</header>
19. 敏感数据掩码
javascript
app.directive('mask-data', {
mounted(el, { value, arg = 'phone' }) {
let maskedValue = ''
if (arg === 'phone') {
maskedValue = value.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
} else if (arg === 'id') {
maskedValue = value.replace(/(\d{6})(\d{8})(\d{4})/, '$1********$3')
} else if (arg === 'email') {
maskedValue = value.replace(/(.{2})(.*)(@.*)/, '$1****$3')
}
el.textContent = maskedValue
}
})
// 使用
<span v-mask-data:phone="user.phone">13800138000</span> <!-- 138****8000 -->
<span v-mask-data:id="user.id">310101199001011234</span> <!-- 310101********1234 -->
<span v-mask-data:email="user.email">test@example.com</span> <!-- te****@example.com -->