perf: Optimize UI performance by simplifying glass effects, removing complex gradients and backdrop-filter, and adding hardware acceleration.

This commit is contained in:
kuekhaoyang
2025-11-19 16:36:11 +08:00
parent b6cebc35bf
commit 0f677ddee3
6 changed files with 88 additions and 136 deletions
+44 -103
View File
@@ -4,34 +4,34 @@
:root {
--font-family-system: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
/* Light Mode Palette */
--bg-color-light: #f0f2f5;
--bg-image-light: linear-gradient(120deg, #fdfbfb 0%, #ebedee 100%);
/* Light Mode Palette - Optimized for Performance */
--bg-color-light: #f2f4f7;
/* Removed complex gradient for solid color or very simple gradient */
--bg-image-light: none;
--text-color-light: #1d1d1f;
--text-color-secondary-light: #6e6e73;
--accent-color-light: #0056b3;
/* Updated from #007aff for WCAG AA compliance (6.85:1 with white) */
--glass-bg-light: rgba(242, 242, 247, 0.8);
--glass-border-light: rgba(255, 255, 255, 0.5);
--shadow-color-light: rgba(0, 0, 0, 0.1);
/* Increased opacity to replace blur effect */
--glass-bg-light: rgba(255, 255, 255, 0.95);
--glass-border-light: rgba(0, 0, 0, 0.05);
--shadow-color-light: rgba(0, 0, 0, 0.05);
/* Dark Mode Palette */
--bg-color-dark: #121212;
--bg-image-dark: linear-gradient(120deg, #272B30 0%, #121212 100%);
--bg-image-dark: linear-gradient(120deg, #1a1a1a 0%, #121212 100%);
--text-color-dark: #f5f5f7;
--text-color-secondary-dark: #8e8e93;
--accent-color-dark: #1A6DBF;
/* Updated for WCAG AA compliance (5.27:1 with white) */
--glass-bg-dark: rgba(28, 28, 30, 0.75);
--glass-border-dark: rgba(60, 60, 60, 0.7);
--glass-bg-dark: rgba(30, 30, 30, 0.9);
--glass-border-dark: rgba(255, 255, 255, 0.1);
--shadow-color-dark: rgba(0, 0, 0, 0.3);
/* Universal Variables */
--radius-2xl: 1.5rem;
--radius-full: 9999px;
--shadow-sm: 0 2px 4px var(--shadow-color);
--shadow-md: 0 4px 12px var(--shadow-color);
--transition-fluid: 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
--shadow-sm: 0 1px 2px var(--shadow-color);
--shadow-md: 0 4px 6px var(--shadow-color);
--transition-fluid: 0.3s ease;
/* Active Theme Variables */
--bg-color: var(--bg-color-light);
@@ -43,7 +43,7 @@
--glass-border: var(--glass-border-light);
--shadow-color: var(--shadow-color-light);
--background: #f0f2f5;
--background: #f2f4f7;
--foreground: #1d1d1f;
}
@@ -346,25 +346,25 @@ select:focus-visible {
animation: gradient-x 3s ease infinite;
}
/* Liquid Glass Components */
/* Liquid Glass Components - OPTIMIZED */
.glass-card {
/* background: var(--glass-bg); */
/* Performance fix: Use solid color with opacity instead of blur */
background: var(--bg-color);
opacity: 0.95;
/* backdrop-filter: blur(8px) saturate(120%); */
/* -webkit-backdrop-filter: blur(8px) saturate(120%); */
opacity: 1;
/* Removed backdrop-filter */
border-radius: var(--radius-2xl);
box-shadow: var(--shadow-md);
box-shadow: var(--shadow-sm);
border: 1px solid var(--glass-border);
transition: transform 0.2s ease-out;
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
/* Hardware acceleration */
transform: translateZ(0);
will-change: transform;
}
.glass-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px var(--shadow-color);
box-shadow: var(--shadow-md);
}
/* Optimize fixed/sticky elements for scrolling */
@@ -373,13 +373,12 @@ nav:where(.sticky, .fixed),
.fixed {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
transform: translateZ(0);
}
.glass-input {
background: var(--glass-bg);
/* Performance fix: Disable blur */
/* backdrop-filter: blur(6px) saturate(120%); */
/* -webkit-backdrop-filter: blur(6px) saturate(120%); */
/* Removed backdrop-filter */
border: 1px solid var(--glass-border);
border-radius: var(--radius-2xl);
color: var(--text-color);
@@ -407,7 +406,7 @@ nav:where(.sticky, .fixed),
.glass-button:hover {
transform: translateY(-2px);
filter: brightness(1.1);
box-shadow: 0 4px 8px var(--shadow-color);
box-shadow: var(--shadow-md);
}
.glass-button:active {
@@ -477,7 +476,7 @@ nav:where(.sticky, .fixed),
min-width: 2.5rem;
height: 2.5rem;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
/* Removed backdrop-filter */
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: var(--radius-2xl);
color: white;
@@ -612,6 +611,8 @@ nav:where(.sticky, .fixed),
/* Reduce paint complexity on scroll */
img {
content-visibility: auto;
/* Hardware acceleration for images */
transform: translateZ(0);
}
/* Disable pointer events during scroll for better performance */
@@ -635,19 +636,18 @@ nav {
}
/* ===========================================
SEARCH HISTORY DROPDOWN - LIQUID GLASS
SEARCH HISTORY DROPDOWN - OPTIMIZED
=========================================== */
/* Search History Dropdown - Absolute positioning under search bar, Liquid Glass effect */
/* Search History Dropdown - Absolute positioning under search bar */
.search-history-dropdown {
/* Removed position: fixed - now using absolute from component */
max-height: 400px;
overflow-y: auto;
/* Liquid Glass effect - Core aesthetic */
background: var(--glass-bg);
backdrop-filter: blur(25px) saturate(180%);
-webkit-backdrop-filter: blur(25px) saturate(180%);
/* Optimized: High opacity background instead of blur */
background: var(--bg-color);
opacity: 0.98;
/* Removed backdrop-filter */
/* Rounded-2xl corners - Universal softness */
border-radius: var(--radius-2xl);
@@ -662,7 +662,7 @@ nav {
/* Smooth appearance animation */
opacity: 0;
transform: translateY(-10px) scale(0.95);
animation: search-dropdown-appear 0.3s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
animation: search-dropdown-appear 0.2s ease-out forwards;
/* Transform origin for natural scaling */
transform-origin: top center;
@@ -670,6 +670,9 @@ nav {
/* Prevent text selection during interaction */
user-select: none;
-webkit-user-select: none;
/* Hardware acceleration */
will-change: transform, opacity;
}
@keyframes search-dropdown-appear {
@@ -722,7 +725,7 @@ nav {
cursor: pointer;
/* Fluid animation - Physics-based transitions */
transition: all var(--transition-fluid);
transition: background-color 0.2s ease;
/* Subtle background for hover/highlight */
background: transparent;
@@ -731,13 +734,11 @@ nav {
/* Hover state - Light interaction */
.search-history-item:hover {
background: color-mix(in srgb, var(--accent-color) 10%, transparent);
transform: translateX(2px);
}
/* Highlighted state - Keyboard navigation */
.search-history-item.highlighted {
background: color-mix(in srgb, var(--accent-color) 15%, transparent);
transform: translateX(4px);
/* Subtle inner-glow effect - Lensing principle */
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-color) 30%, transparent);
@@ -762,7 +763,7 @@ nav {
flex-shrink: 0;
/* Fluid animation */
transition: all 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
transition: all 0.2s ease;
/* Prevent triggering parent hover */
z-index: 1;
@@ -797,64 +798,4 @@ nav {
.search-history-dropdown::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--glass-bg) 80%, transparent);
border-radius: var(--radius-full);
}
.search-history-dropdown::-webkit-scrollbar-thumb:hover {
background: color-mix(in srgb, var(--accent-color) 50%, transparent);
}
/* Empty state styling */
.search-history-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
text-align: center;
}
/* Accessibility - Focus visible states */
.search-history-item:focus-visible {
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
.search-history-remove:focus-visible {
outline: 2px solid var(--accent-color);
outline-offset: 1px;
border-radius: var(--radius-full);
}
/* Mobile optimizations */
@media (max-width: 640px) {
.search-history-dropdown {
/* Increase touch target sizes */
padding: 0.5rem;
}
.search-history-item {
padding: 1rem 0.75rem;
/* Larger minimum touch target */
min-height: 48px;
}
.search-history-remove {
width: 32px;
height: 32px;
}
}
/* Dark mode enhancements */
body.dark .search-history-dropdown,
.dark .search-history-dropdown {
/* Enhanced glass effect in dark mode */
backdrop-filter: blur(30px) saturate(200%);
-webkit-backdrop-filter: blur(30px) saturate(200%);
/* Stronger shadow for depth */
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
/* ===========================================
END SEARCH HISTORY DROPDOWN
=========================================== */
}
+1 -1
View File
@@ -110,7 +110,7 @@ function HomePage() {
willChange: 'transform'
}}>
<div className="max-w-7xl mx-auto px-4">
<div className="bg-[var(--glass-bg)] backdrop-blur-[12px] saturate-[120%] [-webkit-backdrop-filter:blur(12px)_saturate(120%)] border border-[var(--glass-border)] shadow-[var(--shadow-md)] px-6 py-4 rounded-[var(--radius-2xl)]" style={{
<div className="bg-[var(--glass-bg)] border border-[var(--glass-border)] shadow-[var(--shadow-sm)] px-6 py-4 rounded-[var(--radius-2xl)]" style={{
transform: 'translate3d(0, 0, 0)'
}}>
<div className="flex items-center justify-between">
+17 -17
View File
@@ -70,7 +70,7 @@ function PlayerContent() {
setCurrentEpisode(index);
setPlayUrl(episode.url);
setVideoError('');
// Update URL to reflect current episode
const params = new URLSearchParams(searchParams.toString());
params.set('episode', index.toString());
@@ -81,24 +81,24 @@ function PlayerContent() {
<div className="min-h-screen bg-[var(--bg-color)]">
{/* Glass Navbar */}
<nav className="sticky top-0 z-50 pt-4 pb-2 px-4" style={{ transform: 'translateZ(0)' }}>
<div className="max-w-7xl mx-auto bg-[var(--glass-bg)] backdrop-blur-[25px] saturate-[180%] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[0_4px_12px_color-mix(in_srgb,var(--shadow-color)_40%,transparent)] px-4 sm:px-6 py-4">
<div className="max-w-7xl mx-auto bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] px-4 sm:px-6 py-4">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 sm:gap-4 min-w-0">
<button
<button
onClick={() => router.push('/')}
className="flex items-center justify-center hover:opacity-80 transition-opacity flex-shrink-0"
title="返回首页"
>
<Image
src="/icon.png"
alt="KVideo"
width={40}
<Image
src="/icon.png"
alt="KVideo"
width={40}
height={40}
className="object-contain"
/>
</button>
<Button
variant="secondary"
<Button
variant="secondary"
onClick={() => router.back()}
className="flex items-center gap-2"
>
@@ -120,10 +120,10 @@ function PlayerContent() {
<p className="text-[var(--text-color-secondary)]">...</p>
</div>
) : videoError && !videoData ? (
<PlayerError
error={videoError}
onBack={() => router.back()}
onRetry={fetchVideoDetails}
<PlayerError
error={videoError}
onBack={() => router.back()}
onRetry={fetchVideoDetails}
/>
) : (
<div className="grid lg:grid-cols-3 gap-6">
@@ -135,10 +135,10 @@ function PlayerContent() {
currentEpisode={currentEpisode}
onBack={() => router.back()}
/>
<VideoMetadata
videoData={videoData}
source={source}
title={title}
<VideoMetadata
videoData={videoData}
source={source}
title={title}
/>
</div>
+5 -5
View File
@@ -23,7 +23,7 @@ export default function SettingsPage() {
useEffect(() => {
const settings = settingsStore.getSettings();
setSources(settings.sources);
setSources(settings.sources || []);
setSortBy(settings.sortBy);
}, []);
@@ -90,7 +90,7 @@ export default function SettingsPage() {
</button>
<div className="flex items-center gap-4">
<div className="w-12 h-12 flex items-center justify-center rounded-[var(--radius-2xl)] bg-[var(--glass-bg)] backdrop-blur-md border border-[var(--glass-border)]">
<div className="w-12 h-12 flex items-center justify-center rounded-[var(--radius-2xl)] bg-[var(--glass-bg)] border border-[var(--glass-border)]">
<svg className="w-6 h-6 text-[var(--text-color)]" viewBox="0 -960 960 960" fill="currentColor">
<path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z" />
</svg>
@@ -103,7 +103,7 @@ export default function SettingsPage() {
</div>
{/* Source Management */}
<div className="bg-[var(--glass-bg)] backdrop-blur-md border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-md)] p-6 mb-6">
<div className="bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] p-6 mb-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-[var(--text-color)]"></h2>
<div className="flex gap-2">
@@ -139,7 +139,7 @@ export default function SettingsPage() {
</div>
{/* Sort Options */}
<div className="bg-[var(--glass-bg)] backdrop-blur-md border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-md)] p-6 mb-6">
<div className="bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] p-6 mb-6">
<h2 className="text-xl font-semibold text-[var(--text-color)] mb-4"></h2>
<p className="text-sm text-[var(--text-color-secondary)] mb-4">
@@ -161,7 +161,7 @@ export default function SettingsPage() {
</div>
{/* Data Management */}
<div className="bg-[var(--glass-bg)] backdrop-blur-md border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-md)] p-6">
<div className="bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] p-6">
<h2 className="text-xl font-semibold text-[var(--text-color)] mb-4"></h2>
<div className="space-y-3">
<button
+4
View File
@@ -31,6 +31,10 @@ export const MovieCard = memo(function MovieCard({ movie, onMovieClick }: MovieC
onMovieClick(movie);
}}
className="group cursor-pointer"
style={{
contain: 'layout style paint',
contentVisibility: 'auto'
}}
>
<Card hover className="overflow-hidden p-0 h-full" blur={false}>
<div className="relative aspect-[2/3] overflow-hidden bg-[var(--glass-bg)] rounded-[var(--radius-2xl)]">
+17 -10
View File
@@ -4,9 +4,9 @@
import type { VideoSource } from '@/lib/types';
export type SortOption =
export type SortOption =
| 'default'
| 'relevance'
| 'relevance'
| 'latency-asc'
| 'date-desc'
| 'date-asc'
@@ -102,7 +102,14 @@ export const settingsStore = {
}
try {
return JSON.parse(stored);
const parsed = JSON.parse(stored);
// Validate that parsed data has all required properties
return {
sources: Array.isArray(parsed.sources) ? parsed.sources : getDefaultSources(),
sortBy: parsed.sortBy || 'default',
searchHistory: parsed.searchHistory !== undefined ? parsed.searchHistory : true,
watchHistory: parsed.watchHistory !== undefined ? parsed.watchHistory : true,
};
} catch {
return {
sources: getDefaultSources(),
@@ -128,7 +135,7 @@ export const settingsStore = {
if (includeHistory && typeof window !== 'undefined') {
const searchHistory = localStorage.getItem(SEARCH_HISTORY_KEY);
const watchHistory = localStorage.getItem(WATCH_HISTORY_KEY);
if (searchHistory) exportData.searchHistory = JSON.parse(searchHistory);
if (watchHistory) exportData.watchHistory = JSON.parse(watchHistory);
}
@@ -139,19 +146,19 @@ export const settingsStore = {
importSettings(jsonString: string): boolean {
try {
const data = JSON.parse(jsonString);
if (data.settings) {
this.saveSettings(data.settings);
}
if (data.searchHistory && typeof window !== 'undefined') {
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(data.searchHistory));
}
if (data.watchHistory && typeof window !== 'undefined') {
localStorage.setItem(WATCH_HISTORY_KEY, JSON.stringify(data.watchHistory));
}
return true;
} catch {
return false;
@@ -163,12 +170,12 @@ export const settingsStore = {
localStorage.removeItem(SETTINGS_KEY);
localStorage.removeItem(SEARCH_HISTORY_KEY);
localStorage.removeItem(WATCH_HISTORY_KEY);
// Clear all cookies
document.cookie.split(";").forEach((c) => {
document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
});
// Clear cache if available
if ('caches' in window) {
caches.keys().then((names) => {