feat: 일시정지 버튼을 눌렀을 때 별의 움직임이 함께 멈춤
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { useFlightSession } from '@/features/flight-session/model/useFlightSession';
|
||||
import { FlightBackgroundWidget } from '@/widgets/flight-background';
|
||||
import { FlightHudWidget } from '@/widgets/flight-hud';
|
||||
|
||||
export default function FlightPage() {
|
||||
const session = useFlightSession();
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-[calc(100vh-64px)] flex-1 flex-col items-center justify-center overflow-hidden p-6 text-white">
|
||||
<FlightBackgroundWidget />
|
||||
<FlightHudWidget />
|
||||
<FlightBackgroundWidget isPaused={session.isPaused} />
|
||||
<FlightHudWidget {...session} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,15 @@ import { getPrefersReducedMotionMediaQuery } from '@/shared/lib/motion/prefersRe
|
||||
export function FlightStarfieldCanvas({
|
||||
vanishYOffset = -68,
|
||||
centerProtectRadius = 200,
|
||||
isPaused = false,
|
||||
}: {
|
||||
vanishYOffset?: number;
|
||||
centerProtectRadius?: number;
|
||||
isPaused?: boolean;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const starsRef = useRef<FlightStar[]>([]);
|
||||
const vanishXJitterRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
@@ -35,7 +39,10 @@ export function FlightStarfieldCanvas({
|
||||
|
||||
const motionQuery = getPrefersReducedMotionMediaQuery();
|
||||
let prefersReducedMotion = motionQuery.matches;
|
||||
const vanishXJitter = createFlightVanishXJitter();
|
||||
if (vanishXJitterRef.current === null) {
|
||||
vanishXJitterRef.current = createFlightVanishXJitter();
|
||||
}
|
||||
const vanishXJitter = vanishXJitterRef.current ?? 0;
|
||||
|
||||
const setCanvasSize = () => {
|
||||
width = window.innerWidth;
|
||||
@@ -58,7 +65,10 @@ export function FlightStarfieldCanvas({
|
||||
);
|
||||
|
||||
setCanvasSize();
|
||||
let stars: FlightStar[] = createStars();
|
||||
if (starsRef.current.length === 0) {
|
||||
starsRef.current = createStars();
|
||||
}
|
||||
let stars = starsRef.current;
|
||||
|
||||
const applyCenterProtection = (x: number, y: number, alpha: number) => {
|
||||
const centerX = width / 2;
|
||||
@@ -183,6 +193,7 @@ export function FlightStarfieldCanvas({
|
||||
})
|
||||
) {
|
||||
stars[index] = createFlightStar({ width, height, isRespawn: true });
|
||||
starsRef.current = stars;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -198,7 +209,18 @@ export function FlightStarfieldCanvas({
|
||||
drawVignette();
|
||||
};
|
||||
|
||||
const stopAnimation = () => {
|
||||
if (!animationFrameId) return;
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = 0;
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
if (prefersReducedMotion || isPaused) {
|
||||
animationFrameId = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
drawFrame(true);
|
||||
animationFrameId = requestAnimationFrame(render);
|
||||
};
|
||||
@@ -211,8 +233,9 @@ export function FlightStarfieldCanvas({
|
||||
const handleResize = () => {
|
||||
setCanvasSize();
|
||||
stars = createStars();
|
||||
starsRef.current = stars;
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
if (prefersReducedMotion || isPaused) {
|
||||
renderStatic();
|
||||
}
|
||||
};
|
||||
@@ -221,18 +244,20 @@ export function FlightStarfieldCanvas({
|
||||
prefersReducedMotion = event.matches;
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
stopAnimation();
|
||||
renderStatic();
|
||||
return;
|
||||
}
|
||||
|
||||
render();
|
||||
if (!isPaused && !animationFrameId) {
|
||||
render();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
motionQuery.addEventListener('change', handleMotionChange);
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
if (prefersReducedMotion || isPaused) {
|
||||
renderStatic();
|
||||
} else {
|
||||
render();
|
||||
@@ -241,9 +266,9 @@ export function FlightStarfieldCanvas({
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
motionQuery.removeEventListener('change', handleMotionChange);
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
stopAnimation();
|
||||
};
|
||||
}, [vanishYOffset, centerProtectRadius]);
|
||||
}, [vanishYOffset, centerProtectRadius, isPaused]);
|
||||
|
||||
return <canvas ref={canvasRef} className="fixed inset-0 z-0 bg-black pointer-events-none" />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { FlightStarfieldCanvas } from '@/features/flight-starfield';
|
||||
|
||||
export function FlightBackgroundWidget() {
|
||||
return <FlightStarfieldCanvas vanishYOffset={-68} centerProtectRadius={200} />;
|
||||
export function FlightBackgroundWidget({ isPaused }: { isPaused: boolean }) {
|
||||
return (
|
||||
<FlightStarfieldCanvas
|
||||
vanishYOffset={-68}
|
||||
centerProtectRadius={200}
|
||||
isPaused={isPaused}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useFlightSession } from "@/features/flight-session/model/useFlightSession";
|
||||
import { saveCurrentVoyage, saveToHistory } from "@/shared/lib/store";
|
||||
import { Voyage, VoyageStatus } from "@/shared/types";
|
||||
|
||||
@@ -23,16 +22,24 @@ const statusOptions: { value: VoyageStatus; label: string; desc: string }[] = [
|
||||
];
|
||||
const FINISH_HOLD_MS = 1000;
|
||||
|
||||
export function FlightHudWidget() {
|
||||
type FlightHudWidgetProps = {
|
||||
voyage: Voyage | null;
|
||||
isPaused: boolean;
|
||||
formattedTime: string;
|
||||
isCountdownCompleted: boolean;
|
||||
handlePauseToggle: () => void;
|
||||
handleFinish: () => Voyage | null;
|
||||
};
|
||||
|
||||
export function FlightHudWidget({
|
||||
voyage,
|
||||
isPaused,
|
||||
formattedTime,
|
||||
isCountdownCompleted,
|
||||
handlePauseToggle,
|
||||
handleFinish,
|
||||
}: FlightHudWidgetProps) {
|
||||
const router = useRouter();
|
||||
const {
|
||||
voyage,
|
||||
isPaused,
|
||||
formattedTime,
|
||||
isCountdownCompleted,
|
||||
handlePauseToggle,
|
||||
handleFinish,
|
||||
} = useFlightSession();
|
||||
const [isDebriefOpen, setIsDebriefOpen] = useState(false);
|
||||
const [finishedVoyage, setFinishedVoyage] = useState<Voyage | null>(null);
|
||||
const [status, setStatus] = useState<VoyageStatus | null>(null);
|
||||
|
||||
Reference in New Issue
Block a user