Skip to content

Commit e00957e

Browse files
committed
fix: 修复FloatWindow 中的低效事件监听器,改为Motion的用法
1 parent 036a8c1 commit e00957e

2 files changed

Lines changed: 145 additions & 171 deletions

File tree

app/components/float-window/FloatWindow.tsx

Lines changed: 143 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"use client";
22

3-
import { useState, useEffect, useCallback, useRef } from "react";
3+
import { useState, useCallback } from "react";
44
import { usePathname } from "next/navigation";
55
import Image from "next/image";
6+
import { motion, AnimatePresence } from "motion/react";
67
import { activityEventsConfig } from "@/app/types/event";
78
import { cn } from "@/lib/utils";
89
import { X, ChevronUp, ExternalLink, Play } from "lucide-react";
@@ -33,42 +34,6 @@ export function FloatWindow() {
3334
// 仅在首页 (/) 可见
3435
const isHomePage = pathname === "/";
3536

36-
const [position, setPosition] = useState<{ x: number; y: number } | null>(
37-
null,
38-
);
39-
const [isDragging, setIsDragging] = useState(false);
40-
const dragOffset = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
41-
42-
const handlePointerDown = (e: React.PointerEvent) => {
43-
if (e.button !== 0) return;
44-
const el = e.currentTarget as HTMLElement;
45-
const rect = el.getBoundingClientRect();
46-
setIsDragging(true);
47-
dragOffset.current = {
48-
x: e.clientX - rect.left,
49-
y: e.clientY - rect.top,
50-
};
51-
if (!position) setPosition({ x: rect.left, y: rect.top });
52-
el.setPointerCapture(e.pointerId);
53-
};
54-
55-
useEffect(() => {
56-
if (!isDragging) return;
57-
const handlePointerMove = (e: PointerEvent) => {
58-
setPosition({
59-
x: e.clientX - dragOffset.current.x,
60-
y: e.clientY - dragOffset.current.y,
61-
});
62-
};
63-
const handlePointerUp = () => setIsDragging(false);
64-
window.addEventListener("pointermove", handlePointerMove);
65-
window.addEventListener("pointerup", handlePointerUp);
66-
return () => {
67-
window.removeEventListener("pointermove", handlePointerMove);
68-
window.removeEventListener("pointerup", handlePointerUp);
69-
};
70-
}, [isDragging]);
71-
7237
const handleDismiss = useCallback(() => setIsDismissed(true), []);
7338
const handleToggle = useCallback(() => setIsCollapsed((prev) => !prev), []);
7439

@@ -78,147 +43,156 @@ export function FloatWindow() {
7843
const currentEvent = latestEvent;
7944

8045
return (
81-
<div
82-
ref={(node) => {
83-
if (node) node.style.touchAction = "none";
84-
}}
46+
<motion.div
47+
layout
48+
drag
49+
dragMomentum={false}
50+
whileDrag={{ cursor: "grabbing" }}
51+
initial={{ opacity: 0, y: 20, scale: 0.95 }}
52+
animate={{ opacity: 1, y: 0, scale: 1 }}
53+
exit={{ opacity: 0, scale: 0.95 }}
8554
className={cn(
86-
"fixed z-50",
87-
!position && "bottom-6 right-6",
88-
!isDragging && "transition-all duration-300 ease-out",
55+
"fixed z-50 bottom-6 right-6",
8956
isCollapsed ? "w-auto" : "w-[280px]",
90-
isDragging ? "cursor-grabbing" : "cursor-grab",
57+
"cursor-grab active:cursor-grabbing", // tailwind fallback cursor
9158
)}
92-
style={position ? { left: position.x, top: position.y } : undefined}
93-
onPointerDown={handlePointerDown}
59+
onPointerDown={(e) => e.stopPropagation()} // Prevent conflicts
9460
>
95-
{/* 极简折叠状态 */}
96-
{isCollapsed ? (
97-
<button
98-
onClick={handleToggle}
99-
onPointerDown={(e) => e.stopPropagation()}
100-
className={cn(
101-
"group flex items-center gap-2 px-4 py-2",
102-
// Newsprint 风格:锐利边角,纯黑实线边框
103-
"bg-[#111111] text-[#F9F9F7] border border-[#111111]",
104-
"hover:bg-[#F9F9F7] hover:text-[#111111]", // 悬停时反色
105-
"font-mono text-xs uppercase tracking-widest",
106-
"transition-colors duration-200",
107-
"shadow-[4px_4px_0px_0px_#111111] hover:translate-x-[-1px] hover:translate-y-[-1px]",
108-
)}
109-
>
110-
<span className="w-2 h-2 bg-[#CC0000] animate-pulse" />
111-
<span className="font-bold">Latest</span>
112-
<ChevronUp className="w-4 h-4 rotate-180 group-hover:-translate-y-0.5 transition-transform" />
113-
</button>
114-
) : (
115-
/* 展开状态 - 报纸卡片 */
116-
<div
117-
className={cn(
118-
styles.newsprintTexture,
119-
styles.hardShadowHover,
120-
"relative border border-[#111111] dark:border-[#F9F9F7]", // 显式边框
121-
"flex flex-col",
122-
)}
123-
>
124-
{/* Header Bar */}
125-
<div className="flex items-center justify-between px-3 py-2 border-b border-[#111111] dark:border-[#F9F9F7] bg-[#111111] dark:bg-[#F9F9F7]">
126-
<span className="font-mono text-[10px] uppercase tracking-widest font-bold text-[#F9F9F7] dark:text-[#111111]">
127-
The Daily Feed
128-
</span>
129-
<div className="flex items-center gap-2">
130-
<button
131-
onClick={handleToggle}
132-
onPointerDown={(e) => e.stopPropagation()}
133-
className="text-[#F9F9F7] dark:text-[#111111] hover:text-[#CC0000] transition-colors"
134-
aria-label="Minimize"
135-
>
136-
<ChevronUp className="w-3.5 h-3.5" />
137-
</button>
138-
<button
139-
onClick={handleDismiss}
140-
onPointerDown={(e) => e.stopPropagation()}
141-
className="text-[#F9F9F7] dark:text-[#111111] hover:text-[#CC0000] transition-colors"
142-
aria-label="Close"
143-
>
144-
<X className="w-3.5 h-3.5" />
145-
</button>
146-
</div>
147-
</div>
148-
149-
{/* Content */}
150-
<div className="p-0">
151-
{/* 图片区域 - 默认为灰度(暗黑模式除外) */}
152-
<div className="relative aspect-[16/9] border-b border-[#111111] dark:border-[#F9F9F7] overflow-hidden group">
153-
<Image
154-
src={currentEvent.coverUrl}
155-
alt={currentEvent.name}
156-
fill
157-
className="object-cover grayscale dark:grayscale-0 group-hover:grayscale-0 group-hover:sepia transition-all duration-500"
158-
sizes="280px"
159-
draggable={false}
160-
/>
161-
{/* 突发新闻徽章 */}
162-
{!currentEvent.deprecated && (
163-
<div className="absolute top-2 left-2 bg-[#CC0000] text-white px-2 py-0.5 text-[9px] font-mono tracking-widest uppercase font-bold">
164-
Breaking
165-
</div>
166-
)}
167-
</div>
168-
169-
{/* 标题与描述 */}
170-
<div className="p-4 bg-transparent">
171-
<h4 className="font-serif text-lg font-bold leading-tight text-[#111111] dark:text-[#F9F9F7] mb-2">
172-
{currentEvent.name}
173-
</h4>
174-
<p className="font-serif text-sm leading-relaxed text-[#111111]/80 dark:text-[#F9F9F7]/80 line-clamp-3 mb-4 text-justify">
175-
{currentEvent.deprecated
176-
? "This event has concluded. View the archives for full coverage."
177-
: "Join us for this significant community event. Detailed coverage inside."}
178-
</p>
179-
180-
{/* 操作按钮 */}
181-
{currentEvent.deprecated && currentEvent.playback ? (
182-
<a
183-
href={currentEvent.playback}
184-
target="_blank"
185-
rel="noopener noreferrer"
61+
<AnimatePresence mode="wait">
62+
{isCollapsed ? (
63+
<motion.button
64+
key="collapsed"
65+
initial={{ opacity: 0 }}
66+
animate={{ opacity: 1 }}
67+
exit={{ opacity: 0 }}
68+
onClick={handleToggle}
69+
className={cn(
70+
"group flex items-center gap-2 px-4 py-2",
71+
// Newsprint 风格:锐利边角,纯黑实线边框
72+
"bg-[#111111] text-[#F9F9F7] border border-[#111111]",
73+
"hover:bg-[#F9F9F7] hover:text-[#111111]", // 悬停时反色
74+
"font-mono text-xs uppercase tracking-widest",
75+
"transition-colors duration-200",
76+
"shadow-[4px_4px_0px_0px_#111111] hover:translate-x-[-1px] hover:translate-y-[-1px]",
77+
)}
78+
>
79+
<span className="w-2 h-2 bg-[#CC0000] animate-pulse" />
80+
<span className="font-bold">Latest</span>
81+
<ChevronUp className="w-4 h-4 rotate-180 group-hover:-translate-y-0.5 transition-transform" />
82+
</motion.button>
83+
) : (
84+
/* 展开状态 - 报纸卡片 */
85+
<motion.div
86+
key="expanded"
87+
initial={{ opacity: 0 }}
88+
animate={{ opacity: 1 }}
89+
exit={{ opacity: 0 }}
90+
className={cn(
91+
styles.newsprintTexture,
92+
styles.hardShadowHover,
93+
"relative border border-[#111111] dark:border-[#F9F9F7]", // 显式边框
94+
"flex flex-col",
95+
)}
96+
>
97+
{/* Header Bar */}
98+
<div className="flex items-center justify-between px-3 py-2 border-b border-[#111111] dark:border-[#F9F9F7] bg-[#111111] dark:bg-[#F9F9F7]">
99+
<span className="font-mono text-[10px] uppercase tracking-widest font-bold text-[#F9F9F7] dark:text-[#111111]">
100+
The Daily Feed
101+
</span>
102+
<div className="flex items-center gap-2">
103+
<button
104+
onClick={handleToggle}
186105
onPointerDown={(e) => e.stopPropagation()}
187-
className={cn(
188-
"flex items-center justify-center gap-2 w-full px-4 py-2",
189-
"border border-[#111111] dark:border-[#F9F9F7]",
190-
"bg-transparent hover:bg-[#111111] hover:text-[#F9F9F7]",
191-
"dark:hover:bg-[#F9F9F7] dark:hover:text-[#111111]",
192-
"font-mono text-xs uppercase tracking-widest font-bold",
193-
"transition-colors duration-200",
194-
)}
106+
className="text-[#F9F9F7] dark:text-[#111111] hover:text-[#CC0000] transition-colors"
107+
aria-label="Minimize"
195108
>
196-
<Play className="w-3 h-3" />
197-
<span>Watch Replay</span>
198-
</a>
199-
) : (
200-
<a
201-
href={currentEvent.discord}
202-
target="_blank"
203-
rel="noopener noreferrer"
109+
<ChevronUp className="w-3.5 h-3.5" />
110+
</button>
111+
<button
112+
onClick={handleDismiss}
204113
onPointerDown={(e) => e.stopPropagation()}
205-
className={cn(
206-
"flex items-center justify-center gap-2 w-full px-4 py-2",
207-
"bg-[#111111] text-[#F9F9F7]",
208-
"hover:bg-[#CC0000] hover:border-[#CC0000]",
209-
"font-mono text-xs uppercase tracking-widest font-bold",
210-
"transition-colors duration-200",
211-
)}
114+
className="text-[#F9F9F7] dark:text-[#111111] hover:text-[#CC0000] transition-colors"
115+
aria-label="Close"
212116
>
213-
<span>Read More</span>
214-
<ExternalLink className="w-3 h-3" />
215-
</a>
216-
)}
117+
<X className="w-3.5 h-3.5" />
118+
</button>
119+
</div>
217120
</div>
218-
</div>
219-
</div>
220-
)}
221-
</div>
121+
122+
{/* Content */}
123+
<div className="p-0">
124+
{/* 图片区域 - 默认为灰度(暗黑模式除外) */}
125+
<div className="relative aspect-[16/9] border-b border-[#111111] dark:border-[#F9F9F7] overflow-hidden group">
126+
<Image
127+
src={currentEvent.coverUrl}
128+
alt={currentEvent.name}
129+
fill
130+
className="object-cover grayscale dark:grayscale-0 group-hover:grayscale-0 group-hover:sepia transition-all duration-500"
131+
sizes="280px"
132+
draggable={false}
133+
/>
134+
{/* 突发新闻徽章 */}
135+
{!currentEvent.deprecated && (
136+
<div className="absolute top-2 left-2 bg-[#CC0000] text-white px-2 py-0.5 text-[9px] font-mono tracking-widest uppercase font-bold">
137+
Breaking
138+
</div>
139+
)}
140+
</div>
141+
142+
{/* 标题与描述 */}
143+
<div className="p-4 bg-transparent">
144+
<h4 className="font-serif text-lg font-bold leading-tight text-[#111111] dark:text-[#F9F9F7] mb-2">
145+
{currentEvent.name}
146+
</h4>
147+
<p className="font-serif text-sm leading-relaxed text-[#111111]/80 dark:text-[#F9F9F7]/80 line-clamp-3 mb-4 text-justify">
148+
{currentEvent.deprecated
149+
? "This event has concluded. View the archives for full coverage."
150+
: "Join us for this significant community event. Detailed coverage inside."}
151+
</p>
152+
153+
{/* 操作按钮 */}
154+
{currentEvent.deprecated && currentEvent.playback ? (
155+
<a
156+
href={currentEvent.playback}
157+
target="_blank"
158+
rel="noopener noreferrer"
159+
onPointerDown={(e) => e.stopPropagation()}
160+
className={cn(
161+
"flex items-center justify-center gap-2 w-full px-4 py-2",
162+
"border border-[#111111] dark:border-[#F9F9F7]",
163+
"bg-transparent hover:bg-[#111111] hover:text-[#F9F9F7]",
164+
"dark:hover:bg-[#F9F9F7] dark:hover:text-[#111111]",
165+
"font-mono text-xs uppercase tracking-widest font-bold",
166+
"transition-colors duration-200",
167+
)}
168+
>
169+
<Play className="w-3 h-3" />
170+
<span>Watch Replay</span>
171+
</a>
172+
) : (
173+
<a
174+
href={currentEvent.discord}
175+
target="_blank"
176+
rel="noopener noreferrer"
177+
onPointerDown={(e) => e.stopPropagation()}
178+
className={cn(
179+
"flex items-center justify-center gap-2 w-full px-4 py-2",
180+
"bg-[#111111] text-[#F9F9F7]",
181+
"hover:bg-[#CC0000] hover:border-[#CC0000]",
182+
"font-mono text-xs uppercase tracking-widest font-bold",
183+
"transition-colors duration-200",
184+
)}
185+
>
186+
<span>Read More</span>
187+
<ExternalLink className="w-3 h-3" />
188+
</a>
189+
)}
190+
</div>
191+
</div>
192+
</motion.div>
193+
)}
194+
</AnimatePresence>
195+
</motion.div>
222196
);
223197
}
224198

data/event.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"discord": "https://discord.com/invite/8AQZj7sa?event=1432010537402761348",
2424
"playback": "https://involutionhell.com/docs/jobs/event-keynote/event-takeway",
2525
"coverUrl": "./event/careerJourney.png",
26-
"deprecated": true
26+
"deprecated": false
2727
}
2828
]
29-
}
29+
}

0 commit comments

Comments
 (0)