이 글의 성격은 무엇인가요?
질문 / 문제 해결
내용을 설명해주세요
appName : three-body-problem
Next JS에서 3.x 버전으로 latest 업데이트 했습니다.
그런데 로딩에서 넘어가질 않네요.
제가 만든 로딩 예시 코드는 아래와 같습니다.
관련코드도 계속 고쳐라고 해봤으나 로딩바가 넘어가질 않습니다.
“use client”;
import “../utils/apps-in-toss-bridge”;
import React, { useState, useEffect, useRef } from “react”;
import {
markHomeAsVisited,
markInitialLoadComplete,
} from “../utils/initial-load”;
import { initBGM } from “../utils/sound”;
const ASSETS_TO_PRELOAD: string = [“/loading.gif”];
interface InitialAssetLoaderProps {
durationMs?: number;
/**
- loading.gif 이미지 테두리 라운드 크기
-
- Tailwind 클래스 (예: “rounded-2xl”, “rounded-3xl”, “rounded-full”)
-
- 또는 CSS 크기 값 (예: “24px”, “16px”, “2rem”)
*/
imageRounding?: string;
onComplete?: () => void;
}
- 또는 CSS 크기 값 (예: “24px”, “16px”, “2rem”)
export default function InitialAssetLoader({
durationMs = 2000,
imageRounding = “rounded-3xl”,
onComplete,
}: InitialAssetLoaderProps = {}) {
const [progress, setProgress] = useState(0);
const [isLoaded, setIsLoaded] = useState(false);
const [isDismissed, setIsDismissed] = useState(false);
// Store latest onComplete in a ref so changes to onComplete don’t restart the loader effect
const onCompleteRef = useRef(onComplete);
useEffect(() => {
onCompleteRef.current = onComplete;
}, [onComplete]);
// Determine if imageRounding is a Tailwind class or a direct CSS style
const isTailwindClass = imageRounding.startsWith(“rounded”);
const roundedClass = isTailwindClass ? imageRounding : “”;
const borderRadiusStyle = isTailwindClass ? undefined : imageRounding;
useEffect(() => {
markHomeAsVisited();
initBGM();
}, );
useEffect(() => {
let isCancelled = false;
let completedCount = 0;
const totalAssets = ASSETS_TO_PRELOAD.length;
let isPreloadFinished = totalAssets === 0;
const checkComplete = () => {
completedCount++;
if (completedCount >= totalAssets) {
isPreloadFinished = true;
}
};
// Preload image assets safely
if (typeof window !== "undefined") {
ASSETS_TO_PRELOAD.forEach((src) => {
try {
const img = new Image();
let handled = false;
const onDone = () => {
if (handled) return;
handled = true;
checkComplete();
};
img.onload = onDone;
img.onerror = onDone;
img.src = src;
if (img.complete) {
onDone();
}
} catch {
checkComplete();
}
});
}
const startTime =
typeof performance !== "undefined" ? performance.now() : Date.now();
let animationFrameId: number;
const tick = () => {
if (isCancelled) return;
const now =
typeof performance !== "undefined" ? performance.now() : Date.now();
const elapsed = now - startTime;
const timeRatio = Math.min(1, elapsed / durationMs);
let currentProgress: number;
if (isPreloadFinished || elapsed >= durationMs) {
currentProgress = Math.floor(timeRatio * 100);
} else {
currentProgress = Math.min(95, Math.floor(timeRatio * 95));
}
if (elapsed >= durationMs) {
setProgress(100);
setTimeout(() => {
if (!isCancelled) {
setIsLoaded(true);
markInitialLoadComplete();
setTimeout(() => {
if (!isCancelled) {
setIsDismissed(true);
if (onCompleteRef.current) {
onCompleteRef.current();
}
}
}, 500);
}
}, 300);
} else {
setProgress((prev) => Math.max(prev, Math.max(1, currentProgress)));
animationFrameId = requestAnimationFrame(tick);
}
};
animationFrameId = requestAnimationFrame(tick);
return () => {
isCancelled = true;
if (typeof cancelAnimationFrame !== "undefined" && animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
};
}, [durationMs]);
if (isDismissed) return null;
return (
<div
suppressHydrationWarning
onClick={initBGM}
onTouchStart={initBGM}
className={fixed inset-0 z-[99999] flex flex-col items-center justify-center bg-gradient-to-b from-black via-slate-950 to-black text-slate-100 select-none p-6 transition-opacity duration-500 ease-out ${ isLoaded ? "opacity-0 pointer-events-none" : "opacity-100" }}
>
{/* Subdued cosmic background glows */}
{/* Dim background stars */}
<div className="absolute top-12 left-10 text-sky-400/20 text-3xl animate-pulse pointer-events-none">
✦
</div>
<div className="absolute bottom-16 right-12 text-indigo-400/20 text-4xl animate-pulse pointer-events-none">
✦
</div>
<div className="absolute top-1/3 right-10 text-slate-500/20 text-2xl animate-bounce pointer-events-none">
★
</div>
{/* Main Loader Content */}
<div className="relative z-10 w-full max-w-md sm:max-w-lg flex flex-col items-center space-y-6 text-center">
{/* Large Loading GIF with customizable rounded corners */}
<div className="relative flex items-center justify-center w-full">
<img
src="/loading.gif"
alt="Loading"
className={`w-[85vw] sm:w-[380px] max-w-[440px] h-auto object-cover select-none overflow-hidden filter drop-shadow-[0_10px_30px_rgba(0,0,0,0.9)] transition-transform duration-300 ${roundedClass}`}
style={{ borderRadius: borderRadiusStyle }}
/>
</div>
{/* Title and Status Text */}
<div className="space-y-2">
<h2 className="text-2xl sm:text-3xl font-black tracking-tight text-transparent bg-clip-text bg-gradient-to-r from-slate-100 via-sky-200 to-slate-300 drop-shadow">
Three Body Problem
</h2>
<p className="text-xs sm:text-sm font-semibold text-slate-500 tracking-wide">
우주를 다운 받고 있어요...
</p>
</div>
{/* Ultra Dark Progress Bar Container */}
<div className="w-full max-w-sm space-y-2 pt-2">
<div className="w-full bg-black/90 rounded-full h-3 p-0.5 border border-slate-800 shadow-inner relative overflow-hidden">
{/* Animated Progress Fill */}
<div
className="bg-gradient-to-r from-sky-600 via-cyan-500 to-blue-600 h-full rounded-full transition-all duration-200 ease-out relative shadow-[0_0_10px_rgba(56,189,248,0.3)]"
style={{ width: `${progress}%` }}
>
<div className="absolute inset-0 bg-white/20 rounded-full animate-pulse" />
</div>
</div>
<div className="flex items-center justify-between text-xs font-bold text-slate-400 px-1">
<span>삼체 세계 진입 중</span>
<span className="text-sky-400">{progress}%</span>
</div>
</div>
</div>
</div>
);
}
