This commit is contained in:
2026-04-18 13:05:29 +08:00
parent 09d4c0c31b
commit 5032701c38
77 changed files with 8538 additions and 2413 deletions

View File

@@ -8,6 +8,7 @@ interface CharacterAnimatorProps {
className?: string;
style?: React.CSSProperties;
imageClassName?: string;
playbackRate?: number;
}
const DEFAULT_ANIMATIONS: Record<AnimationState, CharacterAnimationConfig> = {
@@ -42,6 +43,7 @@ export const CharacterAnimator: React.FC<CharacterAnimatorProps> = ({
className,
style,
imageClassName,
playbackRate = 1,
}) => {
const [frameIndex, setFrameIndex] = useState(1);
const config =
@@ -51,6 +53,13 @@ export const CharacterAnimator: React.FC<CharacterAnimatorProps> = ({
DEFAULT_ANIMATIONS[AnimationState.IDLE];
const startFrame = config.startFrame ?? 1;
const frameCount = config.frames;
const fps =
typeof config.fps === 'number' && Number.isFinite(config.fps)
? Math.max(1, config.fps)
: 10;
const effectivePlaybackRate = Number.isFinite(playbackRate)
? Math.max(0.1, playbackRate)
: 1;
const animationSignature = [
state,
config.basePath ?? '',
@@ -60,6 +69,8 @@ export const CharacterAnimator: React.FC<CharacterAnimatorProps> = ({
config.extension ?? 'png',
startFrame,
frameCount,
fps,
effectivePlaybackRate,
].join('::');
useEffect(() => {
@@ -73,10 +84,16 @@ export const CharacterAnimator: React.FC<CharacterAnimatorProps> = ({
setFrameIndex(prev => {
return prev >= endFrame ? startFrame : prev + 1;
});
}, 100);
}, Math.max(40, Math.round(1000 / (fps * effectivePlaybackRate))));
return () => window.clearInterval(interval);
}, [animationSignature, frameCount, startFrame]);
}, [
animationSignature,
effectivePlaybackRate,
fps,
frameCount,
startFrame,
]);
const frameNumber = frameIndex.toString().padStart(2, '0');
const normalizedBasePath = config.basePath?.replace(/\/+$/u, '');