自定义指令

日常记录,随时补充

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 -->