Skip to content

Commit 7cb5a78

Browse files
committed
Revert "perf(transition): replace spring with CSS transition for page entrance and preload all chunks"
This reverts commit 3d3a7f7.
1 parent bc462cf commit 7cb5a78

2 files changed

Lines changed: 35 additions & 55 deletions

File tree

‎client/src/components/overlay/FunctionalOverlay.tsx‎

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@
66
* 透明度与交互性(而非卸载),使页面状态跨 Esc/重入周期保留,避免动画重播。
77
* 隐藏时设置 inert 防止焦点落入不可见页面(React 18 不支持 inert prop,直接操作 DOM)。
88
*
9-
* 动画策略:页面切换的 opacity/transform 入场出场使用 CSS transition
10-
* (零 JS 开销,运行在 GPU 合成线程),手势/布局动画保留 Framer Motion。
11-
*
129
* @ai-context: 浮层/弹窗组件:FunctionalOverlay。
1310
*/
14-
import { useEffect, useRef, useState } from 'react';
11+
import { useEffect, useRef } from 'react';
12+
import { motion } from 'framer-motion';
1513
import { cn } from '@/lib/utils';
1614

1715
interface FunctionalOverlayProps {
@@ -27,16 +25,6 @@ interface FunctionalOverlayProps {
2725

2826
export function FunctionalOverlay({ children, visible, className, panelClassName, maskClassName }: FunctionalOverlayProps) {
2927
const rootRef = useRef<HTMLDivElement>(null);
30-
// 延迟 backdrop-blur:动画期间(前 300ms)不加 blur,避免合成层与过渡动画竞争
31-
const [showBlur, setShowBlur] = useState(false);
32-
33-
useEffect(() => {
34-
if (visible) {
35-
const timer = setTimeout(() => setShowBlur(true), 300);
36-
return () => clearTimeout(timer);
37-
}
38-
setShowBlur(false);
39-
}, [visible]);
4028

4129
// 隐藏时设置 inert:阻断指针事件、焦点与辅助技术访问(React 18 无 inert prop)
4230
useEffect(() => {
@@ -49,59 +37,59 @@ export function FunctionalOverlay({ children, visible, className, panelClassName
4937
}
5038
}, [visible]);
5139

40+
// 已移除 aria-hidden:inert 属性已完整覆盖其语义(阻断辅助技术+焦点),
41+
// 同时避免 aria-hidden 同步生效而 inert 异步生效导致的焦点竞态警告
5242
return (
53-
<div
43+
<motion.div
5444
ref={rootRef}
5545
className={cn(
5646
"fixed inset-0 z-10 flex items-center justify-center",
5747
"p-2 sm:p-4 md:p-8",
58-
"pb-16 sm:pb-8 md:pb-8",
59-
// CSS transition: opacity 300ms ease-out(合成器线程,零 JS 开销)
60-
"transition-opacity duration-300 ease-out",
61-
visible ? "opacity-100" : "opacity-0",
48+
"pb-16 sm:pb-8 md:pb-8", // 移动端底部预留 BottomNav 空间
6249
className
6350
)}
51+
initial={{ opacity: 0 }}
52+
animate={{ opacity: visible ? 1 : 0 }}
53+
/* 修复:外层过渡统一为 spring 类型,与内层面板保持一致,避免 ease 与 spring 混用导致动画节奏不协调 */
54+
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
6455
style={{ pointerEvents: visible ? 'auto' : 'none' }}
6556
>
66-
{/* 半透明背景遮罩 — 延迟 backdrop-blur 避免动画期间合成层竞争 */}
57+
{/* 半透明背景遮罩 — 隐藏时移除 backdrop-blur,避免 Electron 中 backdrop-filter 合成层遮挡下方 canvas */}
6758
<div className={cn(
6859
"absolute inset-0 bg-black/20",
69-
"transition-[backdrop-filter] duration-300 ease-out",
70-
showBlur && visible && "backdrop-blur-sm",
60+
visible && "backdrop-blur-sm",
7161
maskClassName,
7262
visible ? "pointer-events-auto" : "pointer-events-none"
7363
)} />
7464

75-
{/* 功能面板 — CSS transition 处理 scale/y/opacity 入场出场 */}
76-
<div
65+
{/* 功能面板 — 隐藏时移除 backdrop-blur,避免 backdrop-filter 合成层遮挡 canvas */}
66+
<motion.div
7767
data-work-area="module-content"
7868
className={cn(
7969
"relative z-10 w-full",
8070
"max-w-5xl",
81-
"max-h-[calc(100vh-5rem)] sm:max-h-[85vh]",
71+
"max-h-[calc(100vh-5rem)] sm:max-h-[85vh]", // 移动端适配底部导航
8272
"overflow-y-auto",
83-
"rounded-2xl sm:rounded-[24px_12px_20px_16px]",
73+
"rounded-2xl sm:rounded-[24px_12px_20px_16px]", // 移动端统一圆角
8474
"bg-transparent",
85-
// CSS transition: 面板从 scale(0.9) + y(30px) 缩放入场
86-
"transition-all duration-300 ease-out",
87-
visible
88-
? "opacity-100 scale-100 translate-y-0"
89-
: "opacity-0 scale-90 translate-y-[30px]",
90-
// backdrop-blur 延迟 300ms 后生效,避免与入场动画竞争
91-
showBlur && visible && "backdrop-blur-2xl",
75+
visible && "backdrop-blur-2xl",
9276
"border border-white/20 dark:border-white/10",
9377
"shadow-[0_8px_40px_rgba(0,0,0,0.3)]",
9478
panelClassName,
9579
visible ? "pointer-events-auto" : "pointer-events-none",
9680
"p-3 sm:p-5 md:p-8"
9781
)}
82+
/* 修复:提示浏览器提前创建合成层,避免动画时触发重绘/重排,提升动画流畅度 */
9883
style={{
9984
willChange: 'transform, opacity',
10085
background: 'linear-gradient(180deg, var(--kb-dive-top) 0%, var(--kb-dive-mid) 42%, var(--kb-dive-bot) 100%)',
10186
}}
87+
initial={{ scale: 0.9, y: 30 }}
88+
animate={visible ? { scale: 1, y: 0 } : { scale: 0.9, y: 30 }}
89+
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
10290
>
10391
{children}
104-
</div>
105-
</div>
92+
</motion.div>
93+
</motion.div>
10694
);
107-
}
95+
}

‎client/src/routes/index.tsx‎

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,27 @@ import AppLayout from '@/components/layout/AppLayout';
88
import { AuthGuard } from '@/lib/auth/AuthGuard';
99
import { prefetchRoute } from '@/utils/scheduler';
1010

11-
// 应用启动后空闲期预加载所有页面 chunk,消除页面切换时的加载等待
12-
// 使用 requestIdleCallback 错峰,避免影响首屏交互帧
11+
// 在应用启动后空闲期预加载核心模块
12+
// P1-6 prefetch 错峰:7 个高频页面 chunk 每 300ms 拉取一个(而非一次性突发),
13+
// 避免启动 2s 后的网络/磁盘峰值影响首次交互帧
1314
if (typeof window !== 'undefined') {
14-
const ALL_PAGES = [
15+
const HIGH_FREQUENCY_PAGES = [
1516
() => import('@/features/dashboard/pages/DashboardPage'),
1617
() => import('@/features/notes/pages/NoteEditPage'),
1718
() => import('@/features/pomodoro/pages/PomodoroPage'),
19+
// 修复:增加高频页面预加载,减少模块切换时的加载等待与视觉跳变
1820
() => import('@/features/notes/pages/NotesPage'),
1921
() => import('@/features/flashcards/pages/FlashcardsPage'),
2022
() => import('@/features/feynman/pages/FeynmanPage'),
2123
() => import('@/pages/SettingsPage'),
22-
() => import('@/features/dashboard/pages/AnalyticsPage'),
23-
() => import('@/features/inspiration/pages/InspirationPage'),
24-
() => import('@/features/classroom/pages/ClassroomPage'),
25-
() => import('@/features/pomodoro/pages/PomodoroStatsPage'),
26-
() => import('@/features/pomodoro/pages/PomodoroSettingsPage'),
27-
() => import('@/features/notes/pages/NotesGraphPage'),
28-
() => import('@/features/flashcards/pages/DeckDetailPage'),
29-
() => import('@/features/flashcards/pages/StudySessionPage'),
30-
() => import('@/features/flashcards/pages/GenerativeReviewPage'),
31-
() => import('@/features/feynman/pages/FeynmanSessionPage'),
32-
() => import('@/features/feynman/pages/FeynmanGraphPage'),
33-
() => import('@/features/feynman/pages/SocraticSessionPage'),
3424
];
35-
const idle = window.requestIdleCallback ?? ((cb: () => void) => setTimeout(cb, 1000));
25+
const PREFETCH_STAGGER_MS = 300;
3626
window.addEventListener('load', () => {
37-
idle(() => {
38-
ALL_PAGES.forEach((loader) => prefetchRoute(loader));
39-
});
27+
setTimeout(() => {
28+
HIGH_FREQUENCY_PAGES.forEach((loader, i) => {
29+
setTimeout(() => prefetchRoute(loader), i * PREFETCH_STAGGER_MS);
30+
});
31+
}, 2000); // 启动2秒后开始预加载
4032
});
4133
}
4234

0 commit comments

Comments
 (0)