初始化
This commit is contained in:
65
client/src/composables/useInteractions.ts
Normal file
65
client/src/composables/useInteractions.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
export const useInteractions = () => {
|
||||
// 滚动进度条
|
||||
const updateScrollProgress = () => {
|
||||
const scrollTop = window.scrollY
|
||||
const docHeight = document.documentElement.scrollHeight
|
||||
const winHeight = window.innerHeight
|
||||
const scrollPercent = scrollTop / (docHeight - winHeight)
|
||||
const progressBar = document.getElementById('scroll-progress')
|
||||
if (progressBar) {
|
||||
progressBar.style.width = `${scrollPercent * 100}%`
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标倾斜效果
|
||||
const initTiltEffect = () => {
|
||||
const tiltCards = document.querySelectorAll('.tilt-card')
|
||||
tiltCards.forEach(card => {
|
||||
card.addEventListener('mousemove', (e) => {
|
||||
const cardRect = card.getBoundingClientRect()
|
||||
const x = e.clientX - cardRect.left
|
||||
const y = e.clientY - cardRect.top
|
||||
|
||||
const xPercent = (x / cardRect.width) * 100
|
||||
const yPercent = (y / cardRect.height) * 100
|
||||
|
||||
card.style.setProperty('--mouse-x', `${xPercent}%`)
|
||||
card.style.setProperty('--mouse-y', `${yPercent}%`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化所有交互功能
|
||||
const init = () => {
|
||||
// 初始化Lucide图标
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons()
|
||||
}
|
||||
|
||||
// 添加滚动事件监听
|
||||
window.addEventListener('scroll', updateScrollProgress)
|
||||
|
||||
// 初始化倾斜效果
|
||||
initTiltEffect()
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
const cleanup = () => {
|
||||
window.removeEventListener('scroll', updateScrollProgress)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
return {
|
||||
updateScrollProgress,
|
||||
initTiltEffect
|
||||
}
|
||||
}
|
||||
35
client/src/composables/useScrollAnimation.ts
Normal file
35
client/src/composables/useScrollAnimation.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export function useScrollAnimation() {
|
||||
let observer: IntersectionObserver | null = null
|
||||
|
||||
const observe = () => {
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible')
|
||||
// Optional: Stop observing once visible
|
||||
// observer?.unobserve(entry.target)
|
||||
}
|
||||
})
|
||||
}, {
|
||||
threshold: 0.1,
|
||||
rootMargin: '50px' // Pre-load slightly before element comes into view
|
||||
})
|
||||
|
||||
document.querySelectorAll('.animate-slide-down, .animate-reveal').forEach(el => {
|
||||
observer?.observe(el)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Small delay to ensure DOM is ready
|
||||
setTimeout(observe, 100)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
43
client/src/composables/useToast.ts
Normal file
43
client/src/composables/useToast.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export const useToast = () => {
|
||||
const showToast = (message: string, type: 'success' | 'error' = 'success') => {
|
||||
const container = document.getElementById('toast-container')
|
||||
if (!container) return
|
||||
|
||||
const toast = document.createElement('div')
|
||||
toast.className = `toast ${type === 'success' ? 'toast-success' : 'toast-error'}`
|
||||
toast.innerHTML = `
|
||||
<i data-lucide="${type === 'success' ? 'check-circle' : 'alert-circle'}" class="w-5 h-5 ${type === 'success' ? 'text-[#d4b383]' : 'text-red-500'}"></i>
|
||||
<span class="text-sm font-medium">${message}</span>
|
||||
`
|
||||
container.appendChild(toast)
|
||||
|
||||
// 初始化Lucide图标
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons()
|
||||
}
|
||||
|
||||
// 显示动画
|
||||
requestAnimationFrame(() => toast.classList.add('show'))
|
||||
|
||||
// 3秒后自动隐藏
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show')
|
||||
setTimeout(() => toast.remove(), 400)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
// 便捷方法
|
||||
const success = (message: string) => {
|
||||
showToast(message, 'success')
|
||||
}
|
||||
|
||||
const error = (message: string) => {
|
||||
showToast(message, 'error')
|
||||
}
|
||||
|
||||
return {
|
||||
showToast,
|
||||
success,
|
||||
error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user