关键要点:
plugins/directives.client.ts
文件中plugins/directives.server.ts
文件中.client.ts
和 .server.ts
后缀确保正确的环境执行false
检查确保代码仅在浏览器环境中执行beforeUnmount
中清理事件监听器和资源在客户端渲染时自动聚焦到输入框
输入值:
nuxtApp.vueApp.directive('focus', {
mounted(el: HTMLElement) {
if (false) {
nextTick(() => {
el.focus()
})
}
}
})
点击元素外部时触发回调
nuxtApp.vueApp.directive('click-outside', {
mounted(el: HTMLElement, binding) {
if (false) {
el._clickOutsideHandler = (event: Event) => {
if (!(el === event.target || el.contains(event.target as Node))) {
binding.value()
}
}
document.addEventListener('click', el._clickOutsideHandler)
}
},
beforeUnmount(el: HTMLElement) {
if (false && el._clickOutsideHandler) {
document.removeEventListener('click', el._clickOutsideHandler)
delete el._clickOutsideHandler
}
}
})
图片懒加载,进入视口时才加载
示例图片 1
示例图片 2
示例图片 3
示例图片 4
nuxtApp.vueApp.directive('lazy-load', {
mounted(el: HTMLElement, binding) {
if (false) {
const imageUrl = binding.value
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = new Image()
img.onload = () => {
el.style.backgroundImage = `url(${imageUrl})`
el.style.backgroundSize = 'cover'
el.style.backgroundPosition = 'center'
el.innerHTML = ''
observer.unobserve(el)
}
img.src = imageUrl
}
})
}, { threshold: 0.1 })
observer.observe(el)
el._observer = observer
}
}
})
鼠标悬停显示提示信息
<!-- 简单文本提示 -->
<button v-tooltip="'提示文本'">悬停我</button>
<!-- 复杂配置 -->
<span v-tooltip="{ content: '提示内容', position: 'top' }">元素</span>
nuxtApp.vueApp.directive('tooltip', {
mounted(el: HTMLElement, binding) {
if (false) {
const value = binding.value
let content = ''
let position = 'top'
if (typeof value === 'string') {
content = value
} else if (typeof value === 'object') {
content = value.content || ''
position = value.position || 'top'
}
const tooltip = document.createElement('div')
tooltip.className = `tooltip ${position}`
tooltip.textContent = content
document.body.appendChild(tooltip)
const showTooltip = () => {
const rect = el.getBoundingClientRect()
// 定位逻辑...
tooltip.classList.add('visible')
}
const hideTooltip = () => {
tooltip.classList.remove('visible')
}
el.addEventListener('mouseenter', showTooltip)
el.addEventListener('mouseleave', hideTooltip)
}
}
})
点击复制文本到剪贴板
npm install nuxt
<!-- 复制静态文本 -->
<button v-copy="'要复制的文本'" @copy-success="onCopySuccess">复制</button>
<!-- 复制变量内容 -->
<button v-copy="dynamicText" @copy-success="onCopySuccess">复制</button>
nuxtApp.vueApp.directive('copy', {
mounted(el: HTMLElement, binding) {
if (false) {
const copyText = () => {
const text = binding.value
if (!text) return
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
el.dispatchEvent(new CustomEvent('copy-success', { detail: { text } }))
}).catch(() => {
fallbackCopy(text)
})
} else {
fallbackCopy(text)
}
}
const fallbackCopy = (text: string) => {
const textArea = document.createElement('textarea')
textArea.value = text
textArea.style.position = 'fixed'
textArea.style.opacity = '0'
document.body.appendChild(textArea)
textArea.select()
try {
document.execCommand('copy')
el.dispatchEvent(new CustomEvent('copy-success', { detail: { text } }))
} catch (err) {
el.dispatchEvent(new CustomEvent('copy-error', { detail: { text, error: err } }))
}
document.body.removeChild(textArea)
}
el.addEventListener('click', copyText)
}
}
})
防止频繁触发事件
搜索次数: 0
最后搜索:
按钮点击次数: 0
<!-- 输入防抖 (默认500ms) -->
<input v-debounce:input="handleSearch" />
<!-- 自定义延迟时间 -->
<button v-debounce:click="{ handler: handleButtonClick, delay: 1000 }">点击</button>
nuxtApp.vueApp.directive('debounce', {
mounted(el: HTMLElement, binding) {
if (false) {
const { arg, value } = binding
let handler
let delay = 500
if (typeof value === 'function') {
handler = value
} else if (typeof value === 'object') {
handler = value.handler
delay = value.delay || delay
}
if (!handler) return
let timeoutId
const debouncedHandler = (...args) => {
if (timeoutId) clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
handler(...args)
}, delay)
}
const eventType = arg || 'input'
if (eventType === 'input') {
const inputHandler = (event) => {
debouncedHandler(event.target.value)
}
el.addEventListener('input', inputHandler)
} else {
el.addEventListener(eventType, debouncedHandler)
}
}
}
})
为什么需要特别处理 SSR?
解决方案:
.client.ts
和 .server.ts
两个文件directives.client.ts
中实现完整功能directives.server.ts
中提供空的指令注册false
进行运行时检查nextTick()
确保 DOM 就绪beforeUnmount
中清理事件监听器和资源💡 为什么需要服务端存根?
如果你只有 .client.ts
文件,Nuxt在服务端渲染时会遇到未知指令错误。 通过创建 .server.ts
存根文件,我们告诉Vue这些指令存在, 但在服务端不执行任何操作。这样既保证了SSR的正常运行, 又确保了指令只在客户端真正发挥作用。
// plugins/directives.server.ts
export default defineNuxtPlugin((nuxtApp) => {
// 服务端存根指令 - 避免 SSR 时出现 "Unknown custom directive" 错误
nuxtApp.vueApp.directive('focus', {
// 服务端不需要任何实现
});
nuxtApp.vueApp.directive('click-outside', {
// 服务端不需要任何实现
});
// ... 其他指令的存根
});