// directives/hoverEffects.ts import { Directive } from 'vue' export interface HoverOptions { type?: 'lift' | 'scale' | 'tilt' | 'glow' | 'rotate' | 'slide-up' | 'slide-down' | 'bounce' | 'shake' duration?: number | string easing?: string intensity?: number // Scale factor for the effect (0.1 = subtle, 1.0 = normal, 2.0 = dramatic) transform?: string // Custom transform for hover state boxShadow?: string // Custom box shadow for hover state backgroundColor?: string // Custom background color for hover state borderColor?: string // Custom border color for hover state color?: string // Custom text color for hover state scale?: number // Custom scale value translateY?: number // Custom Y translation in pixels translateX?: number // Custom X translation in pixels rotateZ?: number // Custom rotation in degrees disabled?: boolean // Disable hover effects } export type HoverValue = string | HoverOptions interface HoverElement extends HTMLElement { _hoverOptions?: HoverOptions _originalStyles?: { transition?: string transform?: string boxShadow?: string backgroundColor?: string borderColor?: string color?: string } _mouseEnterHandler?: (e: MouseEvent) => void _mouseLeaveHandler?: (e: MouseEvent) => void } function setupHoverEffects(el: HoverElement, binding: any) { // Clean up existing handlers if (el._mouseEnterHandler) { el.removeEventListener('mouseenter', el._mouseEnterHandler) el.removeEventListener('mouseleave', el._mouseLeaveHandler!) } let options: HoverOptions = {} if (typeof binding.value === 'string') { options = { type: binding.value } } else if (typeof binding.value === 'object') { options = { ...binding.value } } const { type = 'lift', duration = '0.3s', easing = 'ease', intensity = 1.0, transform, boxShadow, backgroundColor, borderColor, color, scale, translateY, translateX, rotateZ, disabled = false } = options el._hoverOptions = options if (disabled) return // Store original styles const computedStyle = window.getComputedStyle(el) el._originalStyles = { transition: computedStyle.transition, transform: computedStyle.transform, boxShadow: computedStyle.boxShadow, backgroundColor: computedStyle.backgroundColor, borderColor: computedStyle.borderColor, color: computedStyle.color } // Set base transition el.style.transition = `all ${duration} ${easing}` // Get hover effects based on type const hoverEffects = getHoverEffects(type, intensity, { transform, boxShadow, backgroundColor, borderColor, color, scale, translateY, translateX, rotateZ }) // Mouse enter handler el._mouseEnterHandler = (e: MouseEvent) => { Object.assign(el.style, hoverEffects.enter) } // Mouse leave handler el._mouseLeaveHandler = (e: MouseEvent) => { Object.assign(el.style, hoverEffects.leave) } el.addEventListener('mouseenter', el._mouseEnterHandler) el.addEventListener('mouseleave', el._mouseLeaveHandler) } function getHoverEffects( type: string, intensity: number, customValues: Partial ) { const effects = { enter: {} as any, leave: {} as any } // Custom values take precedence if (customValues.transform) { effects.enter.transform = customValues.transform effects.leave.transform = 'none' return effects } // Preset effects switch (type) { case 'lift': effects.enter.transform = `translateY(${customValues.translateY || -8 * intensity}px) scale(${customValues.scale || 1 + 0.02 * intensity})` effects.enter.boxShadow = customValues.boxShadow || `0 ${10 * intensity}px ${25 * intensity}px rgba(0,0,0,0.15)` break case 'scale': effects.enter.transform = `scale(${customValues.scale || 1 + 0.05 * intensity})` break case 'tilt': effects.enter.transform = `perspective(1000px) rotateY(${customValues.rotateZ || 5 * intensity}deg) scale(${1 + 0.02 * intensity})` break case 'glow': effects.enter.boxShadow = customValues.boxShadow || `0 0 ${20 * intensity}px rgba(59, 130, 246, 0.5)` effects.enter.transform = `scale(${1 + 0.02 * intensity})` break case 'rotate': effects.enter.transform = `rotate(${customValues.rotateZ || 5 * intensity}deg) scale(${1 + 0.02 * intensity})` break case 'slide-up': effects.enter.transform = `translateY(${customValues.translateY || -5 * intensity}px)` break case 'slide-down': effects.enter.transform = `translateY(${customValues.translateY || 5 * intensity}px)` break case 'bounce': effects.enter.transform = `translateY(${-3 * intensity}px) scale(${1 + 0.05 * intensity})` effects.enter.transition = `all 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55)` break case 'shake': effects.enter.animation = `shake-${intensity} 0.5s ease-in-out` break } // Add custom color effects if (customValues.backgroundColor) { effects.enter.backgroundColor = customValues.backgroundColor } if (customValues.borderColor) { effects.enter.borderColor = customValues.borderColor } if (customValues.color) { effects.enter.color = customValues.color } // Leave effects (return to original) effects.leave.transform = 'none' effects.leave.boxShadow = '' effects.leave.backgroundColor = '' effects.leave.borderColor = '' effects.leave.color = '' effects.leave.animation = '' return effects } export const hoverEffects: Directive = { mounted(el: HoverElement, binding) { setupHoverEffects(el, binding) }, updated(el: HoverElement, binding) { if (JSON.stringify(binding.value) !== JSON.stringify(binding.oldValue)) { setupHoverEffects(el, binding) } }, unmounted(el: HoverElement) { if (el._mouseEnterHandler) { el.removeEventListener('mouseenter', el._mouseEnterHandler) el.removeEventListener('mouseleave', el._mouseLeaveHandler!) } } } // Add shake keyframes to document if not already added if (typeof window !== 'undefined') { const addShakeKeyframes = () => { if (document.querySelector('#hover-directive-styles')) return const style = document.createElement('style') style.id = 'hover-directive-styles' style.innerHTML = ` @keyframes shake-1 { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-2px); } 75% { transform: translateX(2px); } } @keyframes shake-2 { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } ` document.head.appendChild(style) } // Add styles when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', addShakeKeyframes) } else { addShakeKeyframes() } }