feat: 添加多种新动画效果,包括涟漪扩散、微妙悬浮、脉冲光环等,增强用户交互体验

This commit is contained in:
2026-01-03 17:32:38 +07:00
parent 1c931c40b3
commit e327e1a903
2 changed files with 258 additions and 29 deletions

View File

@@ -235,3 +235,182 @@
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
}
/* 涟漪扩散动画 */
@keyframes ripple-expand {
0% {
transform: translate(-50%, -50%) scale(0);
opacity: 1;
}
100% {
transform: translate(-50%, -50%) scale(40);
opacity: 0;
}
}
/* 微妙的悬浮动画 */
@keyframes float-subtle {
0%, 100% {
transform: translateY(0) translateZ(0);
}
50% {
transform: translateY(-5px) translateZ(10px);
}
}
.animate-float-subtle {
animation: float-subtle 3s ease-in-out infinite;
}
/* 脉冲光环动画 */
@keyframes pulse-ring {
0% {
transform: scale(0.95);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
100% {
transform: scale(0.95);
opacity: 1;
}
}
.animate-pulse-ring {
animation: pulse-ring 2s ease-in-out infinite;
}
/* 发光脉冲动画 */
@keyframes pulse-glow {
0%, 100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
}
.animate-pulse-glow {
animation: pulse-glow 1.5s ease-in-out infinite;
}
/* 微妙的脉冲动画 */
@keyframes pulse-subtle {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.8;
}
}
.animate-pulse-subtle {
animation: pulse-subtle 2s ease-in-out infinite;
}
/* 微妙的滑入动画 */
@keyframes slide-in-subtle {
from {
opacity: 0;
transform: translateX(10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-slide-in-subtle {
animation: slide-in-subtle 0.5s ease-out;
}
/* 3D透视 */
.perspective-1000 {
perspective: 1000px;
}
.transform-style-3d {
transform-style: preserve-3d;
}
.transform-gpu {
transform: translateZ(0);
backface-visibility: hidden;
will-change: transform;
}
/* 3D旋转效果 */
.hover\:rotate-y-2:hover {
transform: translateY(-0.5rem) rotateY(2deg) translateZ(20px);
}
/* 按钮闪光效果 */
@keyframes button-shine {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
.animate-button-shine {
animation: button-shine 2s ease-in-out infinite;
}
/* 震动效果 */
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
25% {
transform: translateX(-5px);
}
75% {
transform: translateX(5px);
}
}
.animate-shake {
animation: shake 0.5s ease-in-out;
}
/* 弹跳效果 */
@keyframes bounce-in {
0% {
opacity: 0;
transform: scale(0.3) translateY(50px);
}
50% {
transform: scale(1.05) translateY(-10px);
}
70% {
transform: scale(0.9) translateY(0);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.animate-bounce-in {
animation: bounce-in 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* 呼吸灯效果 */
@keyframes breathe {
0%, 100% {
opacity: 0.6;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.1);
}
}
.animate-breathe {
animation: breathe 2s ease-in-out infinite;
}

View File

@@ -46,8 +46,42 @@ function openAppDetail(app: AppInfo) {
navigateTo(`/apps/${app.id}`)
}
// 创建涟漪效果
function createRipple(event: MouseEvent | TouchEvent) {
const button = event.currentTarget as HTMLElement
const ripple = document.createElement('span')
const rect = button.getBoundingClientRect()
const x = ('touches' in event ? event.touches[0]!.clientX : event.clientX) - rect.left
const y = ('touches' in event ? event.touches[0]!.clientY : event.clientY) - rect.top
ripple.style.cssText = `
position: absolute;
left: ${x}px;
top: ${y}px;
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
transform: translate(-50%, -50%) scale(0);
animation: ripple-expand 0.6s ease-out;
pointer-events: none;
z-index: 100;
`
button.style.position = 'relative'
button.style.overflow = 'hidden'
button.appendChild(ripple)
setTimeout(() => ripple.remove(), 600)
}
// 下载处理
async function handleDownload(app: AppInfo, type: 'ios' | 'android' | 'h5') {
async function handleDownload(app: AppInfo, type: 'ios' | 'android' | 'h5', event?: MouseEvent | TouchEvent) {
if (event) {
createRipple(event)
}
const url = app.downloads[type]
if (!url) {
@@ -87,14 +121,17 @@ useHead({
<div class="min-h-screen relative overflow-hidden bg-gradient-to-br from-gray-50 via-blue-50/30 to-purple-50/30 dark:from-gray-950 dark:via-blue-950/20 dark:to-purple-950/20">
<!-- 科技感网格背景 -->
<div class="fixed inset-0 opacity-30 dark:opacity-20 pointer-events-none">
<div class="absolute inset-0" style="background-image: linear-gradient(rgba(99, 102, 241, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(99, 102, 241, 0.1) 1px, transparent 1px); background-size: 50px 50px;"></div>
<div class="absolute inset-0 animate-pulse-subtle" style="background-image: linear-gradient(rgba(99, 102, 241, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(99, 102, 241, 0.1) 1px, transparent 1px); background-size: 50px 50px;"></div>
</div>
<!-- 动态发光球体背景 -->
<div class="fixed inset-0 pointer-events-none overflow-hidden">
<div class="absolute top-1/4 -left-48 w-96 h-96 bg-primary-500/20 rounded-full blur-3xl animate-pulse"></div>
<div class="absolute bottom-1/4 -right-48 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl animate-pulse" style="animation-delay: 1s;"></div>
<div class="absolute top-1/2 left-1/2 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl animate-pulse" style="animation-delay: 2s;"></div>
<div class="absolute top-1/4 -left-48 w-96 h-96 bg-primary-500/20 rounded-full blur-3xl animate-breathe"></div>
<div class="absolute bottom-1/4 -right-48 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl animate-breathe" style="animation-delay: 1s;"></div>
<div class="absolute top-1/2 left-1/2 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl animate-breathe" style="animation-delay: 2s;"></div>
<!-- 额外的动态光球 -->
<div class="absolute top-1/3 right-1/4 w-64 h-64 bg-cyan-500/15 rounded-full blur-2xl animate-float" style="animation-delay: 0.5s;"></div>
<div class="absolute bottom-1/3 left-1/3 w-72 h-72 bg-indigo-500/15 rounded-full blur-2xl animate-float" style="animation-delay: 1.5s;"></div>
</div>
<!-- Header -->
@@ -102,11 +139,12 @@ useHead({
<header class="sticky top-0 z-50">
<div class="flex items-center justify-between py-4">
<div class="flex items-center gap-3 group">
<div class="size-10 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-xl relative overflow-hidden transition-all duration-300 group-hover:scale-110">
<div class="size-10 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-xl relative overflow-hidden transition-all duration-300 group-hover:scale-110 animate-gradient shadow-lg shadow-blue-500/50">
<div class="absolute inset-0 bg-gradient-to-tr from-white/0 via-white/20 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700"></div>
<span class="relative z-10">R</span>
<div class="absolute -inset-1 bg-blue-400/30 rounded-xl blur-md opacity-0 group-hover:opacity-100 animate-pulse-ring"></div>
<span class="relative z-10 animate-pulse-subtle">R</span>
</div>
<h1 class="text-xl font-bold bg-gradient-to-r from-gray-900 via-primary-600 to-purple-600 dark:from-white dark:via-primary-400 dark:to-purple-400 bg-clip-text text-transparent">
<h1 class="text-xl font-bold bg-gradient-to-r from-gray-900 via-primary-600 to-purple-600 dark:from-white dark:via-primary-400 dark:to-purple-400 bg-clip-text text-transparent animate-gradient">
{{ t('appName') }}
</h1>
</div>
@@ -162,18 +200,21 @@ useHead({
</div>
<!-- Apps Grid -->
<div v-if="filteredApps.length > 0" class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
<div v-if="filteredApps.length > 0" class="grid md:grid-cols-2 lg:grid-cols-3 gap-6 perspective-1000">
<div
v-for="(app, index) in filteredApps"
:key="app.id"
class="group relative animate-fade-in-up"
class="group relative animate-fade-in-up transform-gpu"
:style="`animation-delay: ${index * 0.05}s`"
>
<!-- 发光边框效果 -->
<div class="absolute -inset-0.5 bg-gradient-to-r from-primary-500 via-purple-500 to-blue-500 rounded-2xl opacity-0 group-hover:opacity-100 blur transition-all duration-500 group-hover:blur-md"></div>
<div class="absolute -inset-0.5 bg-gradient-to-r from-primary-500 via-purple-500 to-blue-500 rounded-2xl opacity-0 group-hover:opacity-100 blur transition-all duration-500 group-hover:blur-md animate-gradient"></div>
<!-- 发光轨迹效果 -->
<div class="absolute -inset-1 bg-gradient-to-r from-transparent via-primary-400/50 to-transparent rounded-2xl opacity-0 group-active:opacity-100 blur-sm transition-opacity duration-300 animate-pulse-glow"></div>
<UCard
class="cursor-pointer backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 transition-all duration-500 hover:shadow-2xl hover:shadow-primary-500/20 hover:-translate-y-2 active:scale-95 active:shadow-lg relative overflow-hidden touch-manipulation"
class="cursor-pointer backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 transition-all duration-500 hover:shadow-2xl hover:shadow-primary-500/20 hover:-translate-y-2 hover:rotate-y-2 active:scale-95 active:shadow-lg active:shadow-primary-500/40 relative overflow-hidden touch-manipulation transform-style-3d"
@click="openAppDetail(app)"
>
<!-- 内部发光效果 -->
@@ -181,23 +222,26 @@ useHead({
<div class="flex items-start gap-4 relative z-10">
<!-- App Icon -->
<div class="size-16 rounded-2xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-2xl shrink-0 shadow-lg shadow-blue-500/50 relative overflow-hidden group-hover:shadow-2xl group-hover:shadow-blue-500/60 transition-all duration-500 group-hover:scale-110 group-hover:rotate-3 group-active:scale-105 group-active:rotate-1 p-2">
<div class="size-16 rounded-2xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-2xl shrink-0 shadow-lg shadow-blue-500/50 relative overflow-hidden group-hover:shadow-2xl group-hover:shadow-blue-500/60 transition-all duration-500 group-hover:scale-110 group-hover:rotate-3 group-active:scale-105 group-active:rotate-1 animate-float-subtle p-2">
<!-- 动态发光效果 -->
<div class="absolute inset-0 bg-gradient-to-tr from-white/0 via-white/30 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] group-active:translate-x-[50%] transition-transform duration-700"></div>
<img :src="app.icon" :alt="app.name" class="size-full object-contain relative z-10 rounded-lg transition-transform duration-300 group-active:scale-95" />
<!-- 脉冲光环 -->
<div class="absolute -inset-2 bg-blue-400/30 rounded-2xl blur-md opacity-0 group-hover:opacity-100 animate-pulse-ring"></div>
<img :src="app.icon" :alt="app.name" class="size-full object-contain relative z-10 rounded-lg transition-all duration-300 group-active:scale-95 group-hover:rotate-[-3deg]" />
</div>
<!-- App Info -->
<div class="flex-1 min-w-0">
<h3 class="font-bold text-lg text-gray-900 dark:text-white truncate">
<div class="flex-1 min-w-0 animate-slide-in-subtle">
<h3 class="font-bold text-lg text-gray-900 dark:text-white truncate group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors duration-300">
{{ app.name }}
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 mt-1">
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 mt-1 transition-colors duration-300 group-hover:text-gray-700 dark:group-hover:text-gray-300">
{{ app.shortDescription[locale as 'zh-CN' | 'en-US'] }}
</p>
<div class="flex items-center gap-3 mt-3 text-xs text-gray-500 dark:text-gray-500">
<span>v{{ app.version }}</span>
<div class="flex items-center gap-3 mt-3 text-xs text-gray-500 dark:text-gray-500 group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors duration-300">
<span class="animate-pulse-subtle">v{{ app.version }}</span>
<span></span>
<span>{{ app.stats.total.toLocaleString() }} {{ locale === 'zh-CN' ? '次下载' : 'downloads' }}</span>
<span class="font-medium">{{ app.stats.total.toLocaleString() }} {{ locale === 'zh-CN' ? '次下载' : 'downloads' }}</span>
</div>
</div>
</div>
@@ -210,27 +254,33 @@ useHead({
label="iOS"
size="sm"
block
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-md touch-manipulation"
@click.stop="handleDownload(app, 'ios')"
/>
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-xl active:shadow-blue-500/60 touch-manipulation relative overflow-hidden group/btn"
@click.stop="(e) => handleDownload(app, 'ios', e)"
>
<span class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent translate-x-[-100%] group-hover/btn:translate-x-[100%] transition-transform duration-500"></span>
</UButton>
<UButton
v-if="app.downloads.android"
icon="i-heroicons-device-tablet"
label="Android"
size="sm"
block
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-md touch-manipulation"
@click.stop="handleDownload(app, 'android')"
/>
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-xl active:shadow-blue-500/60 touch-manipulation relative overflow-hidden group/btn"
@click.stop="(e) => handleDownload(app, 'android', e)"
>
<span class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent translate-x-[-100%] group-hover/btn:translate-x-[100%] transition-transform duration-500"></span>
</UButton>
<UButton
v-if="app.downloads.h5"
icon="i-heroicons-globe-alt"
label="Web"
size="sm"
block
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-md touch-manipulation"
@click.stop="handleDownload(app, 'h5')"
/>
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-xl active:shadow-blue-500/60 touch-manipulation relative overflow-hidden group/btn"
@click.stop="(e) => handleDownload(app, 'h5', e)"
>
<span class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent translate-x-[-100%] group-hover/btn:translate-x-[100%] transition-transform duration-500"></span>
</UButton>
</div>
</UCard>
</div>