Skip to content

Commit 14d643d

Browse files
committed
feat: 添加底部进度条,支持拖动调整播放进度
1 parent 4cae14e commit 14d643d

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

src/components/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import CodePanel from './CodePanel';
55
import GraphView from './GraphView';
66
import DataStructuresPanel from './DataStructuresPanel';
77
import ControlPanel from './ControlPanel';
8+
import ProgressBar from './ProgressBar';
89
import { usePlayback } from '../hooks/usePlayback';
910
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts';
1011
import { generateSteps } from '../algorithm/stepGenerator';
@@ -62,6 +63,7 @@ function App() {
6263
toggle,
6364
goToPrevious,
6465
goToNext,
66+
goToStep,
6567
} = usePlayback({
6668
totalSteps: steps.length,
6769
playbackSpeed: 1000,
@@ -147,6 +149,12 @@ function App() {
147149
onPrevious={goToPrevious}
148150
onNext={goToNext}
149151
/>
152+
153+
<ProgressBar
154+
currentStep={currentStepIndex}
155+
totalSteps={steps.length}
156+
onSeek={goToStep}
157+
/>
150158
</div>
151159
);
152160
}

src/components/ProgressBar.css

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
.progress-bar {
2+
position: fixed;
3+
bottom: 0;
4+
left: 0;
5+
width: 100%;
6+
height: 24px;
7+
background: #1e1e1e;
8+
cursor: pointer;
9+
z-index: 100;
10+
display: flex;
11+
align-items: center;
12+
padding: 0 8px;
13+
box-sizing: border-box;
14+
}
15+
16+
.progress-track {
17+
position: relative;
18+
width: 100%;
19+
height: 8px;
20+
background: #444;
21+
border-radius: 4px;
22+
overflow: visible;
23+
}
24+
25+
.progress-fill {
26+
position: absolute;
27+
top: 0;
28+
left: 0;
29+
height: 100%;
30+
background: #4caf50;
31+
border-radius: 4px 0 0 4px;
32+
transition: width 0.1s ease-out;
33+
}
34+
35+
.progress-thumb {
36+
position: absolute;
37+
top: 50%;
38+
width: 16px;
39+
height: 16px;
40+
background: #4caf50;
41+
border: 2px solid #fff;
42+
border-radius: 50%;
43+
transform: translate(-50%, -50%);
44+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
45+
transition: left 0.1s ease-out;
46+
}
47+
48+
.progress-bar:hover .progress-thumb {
49+
transform: translate(-50%, -50%) scale(1.2);
50+
}
51+
52+
.progress-bar:active .progress-thumb {
53+
transform: translate(-50%, -50%) scale(1.1);
54+
}

src/components/ProgressBar.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useCallback, useRef, useState } from 'react';
2+
import './ProgressBar.css';
3+
4+
interface ProgressBarProps {
5+
currentStep: number;
6+
totalSteps: number;
7+
onSeek: (step: number) => void;
8+
}
9+
10+
export default function ProgressBar({
11+
currentStep,
12+
totalSteps,
13+
onSeek,
14+
}: ProgressBarProps) {
15+
const trackRef = useRef<HTMLDivElement>(null);
16+
const [isDragging, setIsDragging] = useState(false);
17+
18+
const progress = totalSteps > 1 ? (currentStep / (totalSteps - 1)) * 100 : 0;
19+
20+
const calculateStep = useCallback(
21+
(clientX: number) => {
22+
if (!trackRef.current) return currentStep;
23+
const rect = trackRef.current.getBoundingClientRect();
24+
const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
25+
return Math.round(percent * (totalSteps - 1));
26+
},
27+
[totalSteps, currentStep]
28+
);
29+
30+
const handleMouseDown = useCallback(
31+
(e: React.MouseEvent) => {
32+
setIsDragging(true);
33+
onSeek(calculateStep(e.clientX));
34+
},
35+
[calculateStep, onSeek]
36+
);
37+
38+
const handleMouseMove = useCallback(
39+
(e: MouseEvent) => {
40+
if (isDragging) {
41+
onSeek(calculateStep(e.clientX));
42+
}
43+
},
44+
[isDragging, calculateStep, onSeek]
45+
);
46+
47+
const handleMouseUp = useCallback(() => {
48+
setIsDragging(false);
49+
}, []);
50+
51+
// Add global mouse listeners when dragging
52+
useState(() => {
53+
if (isDragging) {
54+
document.addEventListener('mousemove', handleMouseMove);
55+
document.addEventListener('mouseup', handleMouseUp);
56+
return () => {
57+
document.removeEventListener('mousemove', handleMouseMove);
58+
document.removeEventListener('mouseup', handleMouseUp);
59+
};
60+
}
61+
});
62+
63+
return (
64+
<div
65+
className="progress-bar"
66+
ref={trackRef}
67+
onMouseDown={handleMouseDown}
68+
onMouseMove={(e) => isDragging && onSeek(calculateStep(e.clientX))}
69+
onMouseUp={handleMouseUp}
70+
onMouseLeave={handleMouseUp}
71+
>
72+
<div className="progress-track">
73+
<div
74+
className="progress-fill"
75+
style={{ width: `${progress}%` }}
76+
/>
77+
<div
78+
className="progress-thumb"
79+
style={{ left: `${progress}%` }}
80+
/>
81+
</div>
82+
</div>
83+
);
84+
}

0 commit comments

Comments
 (0)