@@ -344,19 +346,31 @@ function getTagLabel(tag: string) {
diff --git a/src/components/TopToolbar.vue b/src/components/TopToolbar.vue
index 72bb4ec..795b296 100644
--- a/src/components/TopToolbar.vue
+++ b/src/components/TopToolbar.vue
@@ -200,14 +200,14 @@ async function saveBackgroundImage() {
height: 40px;
border-radius: 50%;
- /* 液态玻璃效果 - 艳粉主题 */
+ /* 液态玻璃效果 - 艳粉主题(降低 blur 提升性能) */
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.85) 0%,
rgba(255, 228, 242, 0.7) 100%
);
- backdrop-filter: blur(20px) saturate(180%);
- -webkit-backdrop-filter: blur(20px) saturate(180%);
+ backdrop-filter: blur(12px) saturate(180%);
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
/* 艳粉边框和阴影 */
border: 1.5px solid rgba(255, 20, 147, 0.2);
@@ -221,8 +221,14 @@ async function saveBackgroundImage() {
display: flex;
align-items: center;
justify-content: center;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
+
+ /* 性能优化:GPU 加速 + 只过渡 transform 和 opacity */
+ transform: translate3d(0, 0, 0);
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
+ box-shadow 0.3s ease-out,
+ border-color 0.3s ease-out;
+ contain: layout paint;
}
@media (min-width: 640px) {
@@ -246,8 +252,8 @@ async function saveBackgroundImage() {
rgba(51, 65, 85, 0.85) 0%,
rgba(30, 41, 59, 0.75) 100%
);
- backdrop-filter: blur(20px) saturate(150%);
- -webkit-backdrop-filter: blur(20px) saturate(150%);
+ backdrop-filter: blur(12px) saturate(150%);
+ -webkit-backdrop-filter: blur(12px) saturate(150%);
border: 1.5px solid rgba(255, 105, 180, 0.3);
box-shadow:
@@ -259,12 +265,8 @@ async function saveBackgroundImage() {
}
.toolbar-button:hover {
- transform: scale(1.05) translateY(-2px);
- background: linear-gradient(
- 135deg,
- rgba(255, 255, 255, 0.95) 0%,
- rgba(255, 228, 242, 0.85) 100%
- );
+ /* 使用 translate3d 保持 GPU 层 */
+ transform: translate3d(0, -2px, 0) scale(1.05);
box-shadow:
0 12px 24px rgba(255, 20, 147, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.9) inset,
@@ -274,11 +276,6 @@ async function saveBackgroundImage() {
}
.dark .toolbar-button:hover {
- background: linear-gradient(
- 135deg,
- rgba(51, 65, 85, 0.95) 0%,
- rgba(30, 41, 59, 0.85) 100%
- );
box-shadow:
0 12px 24px rgba(255, 105, 180, 0.3),
0 0 0 1px rgba(255, 105, 180, 0.2) inset,
@@ -288,7 +285,7 @@ async function saveBackgroundImage() {
}
.toolbar-button:active {
- transform: scale(0.95);
+ transform: translate3d(0, 0, 0) scale(0.95);
}
/* GitHub 按钮特殊样式 */
diff --git a/src/composables/useClickEffect.ts b/src/composables/useClickEffect.ts
index 1422f54..11c70fa 100644
--- a/src/composables/useClickEffect.ts
+++ b/src/composables/useClickEffect.ts
@@ -1,6 +1,11 @@
/**
* 全局点击特效 composable
* 在屏幕任意位置点击都会显示涟漪特效
+ *
+ * 性能优化:
+ * 1. 使用对象池复用 DOM 元素,避免频繁创建/销毁
+ * 2. 使用 CSS 变量而非 inline style,减少样式计算
+ * 3. 使用 requestAnimationFrame 批量处理
*/
import { onMounted, onUnmounted } from 'vue'
@@ -23,42 +28,68 @@ const defaultOptions: ClickEffectOptions = {
let isInitialized = false
let currentOptions = { ...defaultOptions }
-// 创建点击特效
+// 对象池 - 复用 DOM 元素
+const effectPool: HTMLDivElement[] = []
+const POOL_SIZE = 10 // 最大池大小
+const activeEffects = new Set()
+
+// 从池中获取或创建元素
+function getEffectElement(): HTMLDivElement {
+ let effect = effectPool.pop()
+
+ if (!effect) {
+ effect = document.createElement('div')
+ effect.className = 'global-click-effect'
+ }
+
+ activeEffects.add(effect)
+ return effect
+}
+
+// 回收元素到池中
+function recycleEffect(effect: HTMLDivElement) {
+ activeEffects.delete(effect)
+
+ // 重置样式
+ effect.classList.remove('effect-active')
+
+ // 从 DOM 移除
+ if (effect.parentNode) {
+ effect.parentNode.removeChild(effect)
+ }
+
+ // 如果池未满,回收元素
+ if (effectPool.length < POOL_SIZE) {
+ effectPool.push(effect)
+ }
+}
+
+// 创建点击特效 - 优化版本
function createClickEffect(x: number, y: number) {
if (!currentOptions.enabled) {return}
- const effect = document.createElement('div')
- effect.className = 'global-click-effect'
-
+ const effect = getEffectElement()
const size = currentOptions.size || 100
+ const halfSize = size / 2
- effect.style.cssText = `
- position: fixed;
- left: ${x - size / 2}px;
- top: ${y - size / 2}px;
- width: ${size}px;
- height: ${size}px;
- pointer-events: none;
- z-index: 99999;
- border-radius: 50%;
- background: radial-gradient(circle, ${currentOptions.color} 0%, transparent 70%);
- transform: scale(0);
- animation: global-click-ripple ${currentOptions.duration}ms ease-out forwards;
- `
-
+ // 使用 CSS 变量设置位置和大小
+ effect.style.setProperty('--effect-x', `${x - halfSize}px`)
+ effect.style.setProperty('--effect-y', `${y - halfSize}px`)
+ effect.style.setProperty('--effect-size', `${size}px`)
+
+ // 添加到 DOM 并触发动画
document.body.appendChild(effect)
-
- // 动画结束后移除
- effect.addEventListener('animationend', () => {
- effect.remove()
+
+ // 使用 requestAnimationFrame 确保样式已应用
+ requestAnimationFrame(() => {
+ effect.classList.add('effect-active')
})
- // 备用移除(防止动画事件未触发)
+ // 动画结束后回收
+ const duration = currentOptions.duration || 600
setTimeout(() => {
- if (effect.parentNode) {
- effect.remove()
- }
- }, (currentOptions.duration || 600) + 100)
+ recycleEffect(effect)
+ }, duration + 50)
}
// 处理点击事件
@@ -75,43 +106,48 @@ function handleTouch(event: TouchEvent) {
}
}
-// 注入全局样式
+// 注入全局样式 - 优化版本,使用 CSS 变量
function injectStyles() {
if (document.getElementById('global-click-effect-styles')) {return}
const style = document.createElement('style')
style.id = 'global-click-effect-styles'
style.textContent = `
- @keyframes global-click-ripple {
- 0% {
- transform: scale(0);
- opacity: 1;
- }
- 50% {
- opacity: 0.6;
- }
- 100% {
- transform: scale(2);
- opacity: 0;
- }
+ .global-click-effect {
+ position: fixed;
+ left: var(--effect-x, 0);
+ top: var(--effect-y, 0);
+ width: var(--effect-size, 100px);
+ height: var(--effect-size, 100px);
+ pointer-events: none;
+ z-index: 99999;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(255, 20, 147, 0.4) 0%, transparent 70%);
+ transform: scale(0);
+ opacity: 0;
+ mix-blend-mode: screen;
+ will-change: transform, opacity;
+ contain: strict;
}
- .global-click-effect {
- mix-blend-mode: screen;
+ .global-click-effect.effect-active {
+ animation: global-click-ripple 600ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.dark .global-click-effect {
mix-blend-mode: lighten;
}
- /* 额外的粒子效果 */
- @keyframes particle-fly {
+ @keyframes global-click-ripple {
0% {
- transform: translate(0, 0) scale(1);
+ transform: scale(0);
opacity: 1;
}
+ 50% {
+ opacity: 0.5;
+ }
100% {
- transform: translate(var(--tx), var(--ty)) scale(0);
+ transform: scale(2);
opacity: 0;
}
}
@@ -140,6 +176,17 @@ function destroyClickEffect() {
document.removeEventListener('mousedown', handleClick)
document.removeEventListener('touchstart', handleTouch)
+ // 清理所有活动特效
+ activeEffects.forEach(effect => {
+ if (effect.parentNode) {
+ effect.parentNode.removeChild(effect)
+ }
+ })
+ activeEffects.clear()
+
+ // 清空对象池
+ effectPool.length = 0
+
const styles = document.getElementById('global-click-effect-styles')
if (styles) {
styles.remove()
diff --git a/src/composables/usePerformance.ts b/src/composables/usePerformance.ts
new file mode 100644
index 0000000..db5c405
--- /dev/null
+++ b/src/composables/usePerformance.ts
@@ -0,0 +1,187 @@
+/**
+ * 性能优化 Composable
+ * 提供统一的性能优化工具函数
+ */
+
+/**
+ * 使用 requestIdleCallback 调度低优先级任务
+ * 带有降级回退支持
+ */
+export function scheduleIdleTask(
+ callback: IdleRequestCallback,
+ options?: IdleRequestOptions,
+): number {
+ if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {
+ return window.requestIdleCallback(callback, options)
+ }
+ // 降级到 setTimeout,模拟空闲时执行
+ return setTimeout(() => {
+ callback({
+ didTimeout: false,
+ timeRemaining: () => 50,
+ })
+ }, 16) as unknown as number
+}
+
+/**
+ * 取消空闲任务
+ */
+export function cancelIdleTask(handle: number): void {
+ if (typeof window !== 'undefined' && 'cancelIdleCallback' in window) {
+ window.cancelIdleCallback(handle)
+ } else {
+ clearTimeout(handle)
+ }
+}
+
+/**
+ * 使用 requestAnimationFrame 调度动画帧任务
+ * 返回 Promise 便于 async/await 使用
+ */
+export function nextFrame(): Promise {
+ return new Promise((resolve) => {
+ requestAnimationFrame(resolve)
+ })
+}
+
+/**
+ * 等待多个动画帧(用于确保 DOM 更新完成)
+ */
+export async function waitFrames(count = 2): Promise {
+ for (let i = 0; i < count; i++) {
+ await nextFrame()
+ }
+}
+
+/**
+ * 检测用户是否偏好减少动效
+ */
+export function prefersReducedMotion(): boolean {
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches
+}
+
+/**
+ * 检测设备是否为低性能设备
+ * 基于 hardwareConcurrency 和 deviceMemory
+ */
+export function isLowEndDevice(): boolean {
+ const nav = navigator as Navigator & {
+ deviceMemory?: number
+ connection?: { effectiveType?: string; saveData?: boolean }
+ }
+
+ // 检测 CPU 核心数
+ const cores = navigator.hardwareConcurrency || 4
+ if (cores <= 2) {return true}
+
+ // 检测设备内存(Chrome 特性)
+ if (nav.deviceMemory && nav.deviceMemory <= 2) {return true}
+
+ // 检测网络连接类型
+ if (nav.connection) {
+ if (nav.connection.saveData) {return true}
+ if (nav.connection.effectiveType === 'slow-2g' ||
+ nav.connection.effectiveType === '2g') {
+ return true
+ }
+ }
+
+ return false
+}
+
+/**
+ * 创建节流的滚动处理函数
+ * 使用 requestAnimationFrame 优化
+ */
+export function createScrollHandler(
+ callback: (scrollY: number) => void,
+): () => void {
+ let ticking = false
+ let lastScrollY = 0
+
+ const handler = () => {
+ lastScrollY = window.scrollY
+
+ if (!ticking) {
+ requestAnimationFrame(() => {
+ callback(lastScrollY)
+ ticking = false
+ })
+ ticking = true
+ }
+ }
+
+ return handler
+}
+
+/**
+ * 创建 resize 观察器
+ * 使用 ResizeObserver 替代 resize 事件
+ */
+export function createResizeObserver(
+ element: Element,
+ callback: (entry: ResizeObserverEntry) => void,
+): ResizeObserver {
+ const observer = new ResizeObserver((entries) => {
+ for (const entry of entries) {
+ callback(entry)
+ }
+ })
+
+ observer.observe(element)
+ return observer
+}
+
+/**
+ * 预加载图片
+ */
+export function preloadImage(src: string): Promise {
+ return new Promise((resolve, reject) => {
+ const img = new Image()
+ img.onload = () => resolve(img)
+ img.onerror = reject
+ img.src = src
+ })
+}
+
+/**
+ * 预加载多张图片(并发限制)
+ */
+export async function preloadImages(
+ srcs: string[],
+ concurrency = 3,
+): Promise {
+ const results: HTMLImageElement[] = []
+ const queue = [...srcs]
+
+ const worker = async () => {
+ while (queue.length > 0) {
+ const src = queue.shift()
+ if (src) {
+ try {
+ const img = await preloadImage(src)
+ results.push(img)
+ } catch {
+ // 静默处理失败的图片
+ }
+ }
+ }
+ }
+
+ // 创建并发 worker
+ const workers = Array.from({ length: concurrency }, () => worker())
+ await Promise.all(workers)
+
+ return results
+}
+
+/**
+ * 延迟执行(用于非关键任务)
+ */
+export function defer(fn: () => T, delay = 0): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(fn())
+ }, delay)
+ })
+}
diff --git a/src/stores/search.ts b/src/stores/search.ts
index 63458b5..4de9399 100644
--- a/src/stores/search.ts
+++ b/src/stores/search.ts
@@ -124,7 +124,7 @@ export const useSearchStore = defineStore('search', () => {
})
const totalResults = computed(() =>
Array.from(platformResults.value.values())
- .reduce((sum, platform) => sum + platform.items.length, 0)
+ .reduce((sum, platform) => sum + platform.items.length, 0),
)
// 方法
diff --git a/src/styles/base.css b/src/styles/base.css
index 32e7a4e..3684cba 100644
--- a/src/styles/base.css
+++ b/src/styles/base.css
@@ -4,6 +4,18 @@
*/
@layer base {
+ /* ============================================
+ 性能优化 - 全局渲染提示
+ ============================================ */
+
+ /* 启用硬件加速的根元素 */
+ html {
+ /* 文本渲染优化 */
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+
/* body 基础样式 */
body {
/* 字体栈 - Google Noto Sans SC + Apple System 备用 */
@@ -72,5 +84,151 @@
.lozad[data-loaded="true"] {
opacity: 1;
}
+
+ /* ============================================
+ 性能优化工具类
+ ============================================ */
+
+ /* GPU 加速层 - 用于需要频繁重绘的元素 */
+ .gpu-layer {
+ transform: translateZ(0);
+ backface-visibility: hidden;
+ perspective: 1000px;
+ }
+
+ /* 隔离渲染层 - 防止布局抖动影响其他元素 */
+ .contain-layout {
+ contain: layout;
+ }
+
+ .contain-paint {
+ contain: paint;
+ }
+
+ .contain-strict {
+ contain: strict;
+ }
+
+ .contain-content {
+ contain: content;
+ }
+
+ /* 内容可见性优化 - 延迟渲染屏幕外内容 */
+ .content-auto {
+ content-visibility: auto;
+ contain-intrinsic-size: auto 500px;
+ }
+
+ /* 高性能动画 - 仅使用 transform 和 opacity */
+ .perf-animate {
+ will-change: transform, opacity;
+ }
+
+ /* 滚动性能优化 */
+ .scroll-contain {
+ overscroll-behavior: contain;
+ }
+
+ /* 触摸优化 */
+ .touch-optimize {
+ touch-action: manipulation;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ /* 减少重绘的过渡动画 */
+ .transition-gpu {
+ transition-property: transform, opacity;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 200ms;
+ }
+
+ /* ============================================
+ 响应式性能优化
+ ============================================ */
+
+ /* 减少动效偏好 - 尊重用户系统设置 */
+ @media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+ }
+
+ /* 低数据模式 - 减少动画复杂度 */
+ @media (prefers-reduced-data: reduce) {
+ .gpu-layer {
+ transform: none;
+ perspective: none;
+ }
+ }
+
+ /* ============================================
+ 批量动画延迟 - 用于列表项交错动画
+ ============================================ */
+
+ .stagger-1 { animation-delay: 50ms; }
+ .stagger-2 { animation-delay: 100ms; }
+ .stagger-3 { animation-delay: 150ms; }
+ .stagger-4 { animation-delay: 200ms; }
+ .stagger-5 { animation-delay: 250ms; }
+
+ /* ============================================
+ 渲染优化提示
+ ============================================ */
+
+ /* 仅在 hover 时激活 will-change(节省 GPU 内存) */
+ .will-change-hover:hover {
+ will-change: transform;
+ }
+
+ /* 固定元素渲染隔离 */
+ .fixed-layer {
+ position: fixed;
+ contain: layout paint;
+ isolation: isolate;
+ }
+
+ /* 图片渲染优化 */
+ .img-optimize {
+ image-rendering: -webkit-optimize-contrast;
+ image-rendering: crisp-edges;
+ }
+
+ /* 高质量图片渲染 */
+ .img-quality {
+ image-rendering: smooth;
+ image-rendering: high-quality;
+ }
+
+ /* ============================================
+ 交互优化
+ ============================================ */
+
+ /* 防止双击缩放(移动端) */
+ .no-zoom {
+ touch-action: manipulation;
+ }
+
+ /* 硬件加速滚动 */
+ .scroll-smooth-gpu {
+ scroll-behavior: smooth;
+ -webkit-overflow-scrolling: touch;
+ overscroll-behavior: contain;
+ }
+
+ /* 可拖拽区域优化 */
+ .draggable {
+ user-select: none;
+ touch-action: none;
+ cursor: grab;
+ }
+
+ .draggable:active {
+ cursor: grabbing;
+ }
}
diff --git a/src/styles/glassmorphism.css b/src/styles/glassmorphism.css
index ce39893..a9b7aea 100644
--- a/src/styles/glassmorphism.css
+++ b/src/styles/glassmorphism.css
@@ -49,7 +49,6 @@
saturate(var(--glass-saturate))
brightness(var(--glass-brightness));
border: 1px solid var(--glass-border-light);
- will-change: backdrop-filter;
}
.dark .glass {
@@ -61,67 +60,108 @@
.glassmorphism-input {
background: linear-gradient(
135deg,
- rgba(255, 255, 255, 0.75) 0%,
- rgba(var(--glass-pink-pale), 0.55) 100%
+ rgba(255, 255, 255, 0.88) 0%,
+ rgba(255, 240, 248, 0.75) 50%,
+ rgba(255, 228, 242, 0.65) 100%
);
backdrop-filter:
- blur(var(--glass-blur))
- saturate(var(--glass-saturate))
- brightness(var(--glass-brightness));
+ blur(16px)
+ saturate(200%)
+ brightness(1.08);
-webkit-backdrop-filter:
- blur(var(--glass-blur))
- saturate(var(--glass-saturate))
- brightness(var(--glass-brightness));
- border: 1.5px solid var(--glass-border-light);
+ blur(16px)
+ saturate(200%)
+ brightness(1.08);
+ border: 2px solid rgba(var(--glass-pink), 0.15);
box-shadow:
- 0 4px 16px rgba(var(--glass-pink), 0.06),
- inset 0 1px 0 rgba(255, 255, 255, 0.8);
- transition: all 0.25s ease-out;
- will-change: transform, box-shadow;
+ 0 4px 24px rgba(var(--glass-pink), 0.08),
+ 0 1px 2px rgba(0, 0, 0, 0.04),
+ inset 0 2px 0 rgba(255, 255, 255, 0.9),
+ inset 0 -1px 0 rgba(var(--glass-pink), 0.05);
+ transition:
+ transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
+ box-shadow 0.3s ease-out,
+ border-color 0.3s ease-out,
+ background 0.3s ease-out;
}
.glassmorphism-input:hover {
- border-color: var(--glass-border-light-hover);
+ border-color: rgba(var(--glass-pink), 0.25);
+ background: linear-gradient(
+ 135deg,
+ rgba(255, 255, 255, 0.92) 0%,
+ rgba(255, 240, 248, 0.82) 50%,
+ rgba(255, 228, 242, 0.72) 100%
+ );
box-shadow:
- 0 6px 20px rgba(var(--glass-pink), 0.1),
- inset 0 1px 0 rgba(255, 255, 255, 0.9);
+ 0 8px 32px rgba(var(--glass-pink), 0.12),
+ 0 2px 4px rgba(0, 0, 0, 0.04),
+ inset 0 2px 0 rgba(255, 255, 255, 1),
+ inset 0 -1px 0 rgba(var(--glass-pink), 0.08);
}
.glassmorphism-input:focus {
- border-color: rgba(var(--glass-pink), 0.35);
+ border-color: rgba(var(--glass-pink), 0.45);
+ background: linear-gradient(
+ 135deg,
+ rgba(255, 255, 255, 0.95) 0%,
+ rgba(255, 245, 250, 0.88) 50%,
+ rgba(255, 235, 245, 0.8) 100%
+ );
box-shadow:
- 0 8px 24px rgba(var(--glass-pink), 0.15),
- inset 0 1px 0 rgba(255, 255, 255, 1),
- 0 0 0 3px rgba(var(--glass-pink), 0.08);
- transform: scale(1.005);
+ 0 12px 40px rgba(var(--glass-pink), 0.18),
+ 0 4px 8px rgba(0, 0, 0, 0.04),
+ 0 0 0 4px rgba(var(--glass-pink), 0.1),
+ inset 0 2px 0 rgba(255, 255, 255, 1),
+ inset 0 -1px 0 rgba(var(--glass-pink), 0.1);
+ transform: scale(1.01);
}
.dark .glassmorphism-input {
background: linear-gradient(
135deg,
- rgba(30, 41, 59, 0.82) 0%,
- rgba(51, 65, 85, 0.72) 100%
+ rgba(30, 41, 59, 0.88) 0%,
+ rgba(45, 55, 75, 0.78) 50%,
+ rgba(55, 65, 85, 0.7) 100%
);
- border-color: var(--glass-border-dark);
+ border: 2px solid rgba(var(--glass-pink-light), 0.2);
box-shadow:
- 0 4px 16px rgba(var(--glass-pink-light), 0.12),
- inset 0 1px 0 rgba(255, 255, 255, 0.08);
+ 0 4px 24px rgba(var(--glass-pink-light), 0.15),
+ 0 1px 2px rgba(0, 0, 0, 0.2),
+ inset 0 1px 0 rgba(255, 255, 255, 0.08),
+ inset 0 -1px 0 rgba(var(--glass-pink-light), 0.1);
}
.dark .glassmorphism-input:hover {
- border-color: var(--glass-border-dark-hover);
+ border-color: rgba(var(--glass-pink-light), 0.3);
+ background: linear-gradient(
+ 135deg,
+ rgba(35, 46, 64, 0.92) 0%,
+ rgba(50, 60, 80, 0.82) 50%,
+ rgba(60, 70, 90, 0.75) 100%
+ );
box-shadow:
- 0 6px 20px rgba(var(--glass-pink-light), 0.18),
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ 0 8px 36px rgba(var(--glass-pink-light), 0.2),
+ 0 2px 4px rgba(0, 0, 0, 0.2),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
+ inset 0 -1px 0 rgba(var(--glass-pink-light), 0.12);
}
.dark .glassmorphism-input:focus {
- border-color: rgba(var(--glass-pink-light), 0.45);
+ border-color: rgba(var(--glass-pink-light), 0.5);
+ background: linear-gradient(
+ 135deg,
+ rgba(40, 51, 69, 0.95) 0%,
+ rgba(55, 65, 85, 0.88) 50%,
+ rgba(65, 75, 95, 0.8) 100%
+ );
box-shadow:
- 0 8px 24px rgba(var(--glass-pink-light), 0.25),
+ 0 12px 48px rgba(var(--glass-pink-light), 0.28),
+ 0 4px 8px rgba(0, 0, 0, 0.2),
+ 0 0 0 4px rgba(var(--glass-pink-light), 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.12),
- 0 0 0 3px rgba(var(--glass-pink-light), 0.12);
- transform: scale(1.005);
+ inset 0 -1px 0 rgba(var(--glass-pink-light), 0.15);
+ transform: scale(1.01);
}
/* ========== 搜索按钮玻璃效果 ========== */
@@ -141,8 +181,7 @@
box-shadow:
0 4px 16px rgba(var(--glass-pink), 0.18),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
- transition: all 0.2s ease-out;
- will-change: transform, box-shadow;
+ transition: transform 0.2s ease-out, box-shadow 0.2s ease-out, border-color 0.2s ease-out;
}
.glassmorphism-button:hover:not(:disabled) {
@@ -209,8 +248,7 @@
box-shadow:
0 2px 8px rgba(var(--glass-pink), 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.25);
- transition: all 0.2s ease-out;
- will-change: transform;
+ transition: transform 0.2s ease-out, box-shadow 0.2s ease-out, border-color 0.2s ease-out;
}
.glassmorphism-search-button:hover:not(:disabled) {
@@ -254,46 +292,52 @@
.glassmorphism-mode-switch {
background: linear-gradient(
135deg,
- rgba(255, 255, 255, 0.65) 0%,
- rgba(var(--glass-pink-pale), 0.5) 100%
+ rgba(255, 255, 255, 0.82) 0%,
+ rgba(255, 245, 250, 0.72) 50%,
+ rgba(255, 235, 245, 0.65) 100%
);
backdrop-filter:
- blur(var(--glass-blur))
- saturate(var(--glass-saturate));
+ blur(14px)
+ saturate(180%);
-webkit-backdrop-filter:
- blur(var(--glass-blur))
- saturate(var(--glass-saturate));
- border: 1.5px solid var(--glass-border-light);
+ blur(14px)
+ saturate(180%);
+ border: 2px solid rgba(var(--glass-pink), 0.12);
box-shadow:
- 0 4px 16px rgba(var(--glass-pink), 0.08),
- inset 0 1px 0 rgba(255, 255, 255, 0.85);
- transition: all 0.25s ease-out;
+ 0 4px 20px rgba(var(--glass-pink), 0.1),
+ 0 1px 2px rgba(0, 0, 0, 0.03),
+ inset 0 1px 0 rgba(255, 255, 255, 0.9);
+ transition: all 0.3s ease-out;
}
.glassmorphism-mode-switch:hover {
- border-color: var(--glass-border-light-hover);
+ border-color: rgba(var(--glass-pink), 0.2);
box-shadow:
- 0 6px 20px rgba(var(--glass-pink), 0.12),
- inset 0 1px 0 rgba(255, 255, 255, 0.95);
+ 0 6px 24px rgba(var(--glass-pink), 0.15),
+ 0 2px 4px rgba(0, 0, 0, 0.04),
+ inset 0 1px 0 rgba(255, 255, 255, 1);
}
.dark .glassmorphism-mode-switch {
background: linear-gradient(
135deg,
- rgba(30, 41, 59, 0.78) 0%,
- rgba(51, 65, 85, 0.68) 100%
+ rgba(30, 41, 59, 0.85) 0%,
+ rgba(45, 55, 75, 0.75) 50%,
+ rgba(55, 65, 85, 0.68) 100%
);
- border-color: var(--glass-border-dark);
+ border: 2px solid rgba(var(--glass-pink-light), 0.18);
box-shadow:
- 0 4px 16px rgba(var(--glass-pink-light), 0.12),
- inset 0 1px 0 rgba(255, 255, 255, 0.08);
+ 0 4px 20px rgba(var(--glass-pink-light), 0.15),
+ 0 1px 2px rgba(0, 0, 0, 0.15),
+ inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.dark .glassmorphism-mode-switch:hover {
- border-color: var(--glass-border-dark-hover);
+ border-color: rgba(var(--glass-pink-light), 0.28);
box-shadow:
- 0 6px 20px rgba(var(--glass-pink-light), 0.18),
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ 0 6px 28px rgba(var(--glass-pink-light), 0.22),
+ 0 2px 4px rgba(0, 0, 0, 0.15),
+ inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
/* ========== 模态框液态玻璃效果 ========== */
@@ -316,8 +360,7 @@
0 8px 32px rgba(var(--glass-pink), 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
border-radius: 24px;
- transition: all 0.3s ease-out;
- will-change: transform, box-shadow;
+ transition: transform 0.3s ease-out, box-shadow 0.3s ease-out, border-color 0.3s ease-out;
}
.glassmorphism-modal:hover {
@@ -366,8 +409,7 @@
0 8px 32px rgba(var(--glass-pink), 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.95);
border-radius: 20px;
- transition: all 0.3s ease-out;
- will-change: transform;
+ transition: transform 0.3s ease-out, box-shadow 0.3s ease-out, border-color 0.3s ease-out;
}
.glassmorphism-panel:hover {
@@ -416,8 +458,7 @@
0 2px 8px rgba(var(--glass-pink), 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.5);
border-radius: 12px;
- transition: all 0.2s ease-out;
- will-change: transform;
+ transition: transform 0.2s ease-out, background 0.2s ease-out, box-shadow 0.2s ease-out, border-color 0.2s ease-out;
}
.glassmorphism-toolbar-button:hover {
@@ -490,8 +531,7 @@
0 4px 16px rgba(var(--glass-pink), 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.5);
border-radius: 16px;
- transition: all 0.25s ease-out;
- will-change: transform;
+ transition: transform 0.25s ease-out, box-shadow 0.25s ease-out, border-color 0.25s ease-out;
}
.glassmorphism-card:hover {
@@ -539,8 +579,7 @@
box-shadow:
0 4px 16px rgba(var(--glass-pink), 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.6);
- transition: all 0.2s ease-out;
- will-change: transform, box-shadow;
+ transition: transform 0.2s ease-out, box-shadow 0.2s ease-out, border-color 0.2s ease-out;
}
.glassmorphism-fab:hover {
@@ -632,3 +671,51 @@
-webkit-backdrop-filter: none;
}
}
+
+/* 低电量模式 / 省电模式降级 */
+@media (prefers-reduced-data: reduce) {
+ .glass,
+ .glassmorphism-input,
+ .glassmorphism-button,
+ .glassmorphism-modal,
+ .glassmorphism-panel,
+ .glassmorphism-card,
+ .glassmorphism-fab {
+ backdrop-filter: blur(4px);
+ -webkit-backdrop-filter: blur(4px);
+ }
+}
+
+/* ========== 性能提示:will-change 自动管理 ========== */
+/* 仅在 hover 时激活 will-change,避免长期占用 GPU 内存 */
+.glassmorphism-card:hover,
+.glassmorphism-panel:hover,
+.glassmorphism-modal:hover,
+.glassmorphism-fab:hover,
+.glassmorphism-button:hover {
+ will-change: transform;
+}
+
+/* 触摸设备的优化 */
+@media (hover: none) {
+ .glassmorphism-card,
+ .glassmorphism-panel,
+ .glassmorphism-modal,
+ .glassmorphism-fab,
+ .glassmorphism-button {
+ /* 触摸设备禁用 hover 状态的 will-change 以节省资源 */
+ will-change: auto;
+ }
+}
+
+/* 渲染隔离 - 防止 backdrop-filter 影响其他层 */
+.glass,
+.glassmorphism-input,
+.glassmorphism-button,
+.glassmorphism-modal,
+.glassmorphism-panel,
+.glassmorphism-card,
+.glassmorphism-fab {
+ isolation: isolate;
+ contain: paint;
+}