ne minor, a ebat fix

This commit is contained in:
aurinex
2025-12-14 21:14:59 +05:00
parent de616ee8ac
commit ae4a67dcdf
20 changed files with 1818 additions and 652 deletions

View File

@ -9,6 +9,9 @@ interface SkinViewerProps {
autoRotate?: boolean;
}
const DEFAULT_SKIN =
'https://static.planetminecraft.com/files/resource_media/skin/original-steve-15053860.png';
export default function SkinViewer({
width = 300,
height = 400,
@ -19,57 +22,111 @@ export default function SkinViewer({
}: SkinViewerProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const viewerRef = useRef<any>(null);
const animRef = useRef<any>(null);
// 1) Инициализируем viewer ОДИН РАЗ
useEffect(() => {
if (!canvasRef.current) return;
let disposed = false;
const init = async () => {
if (!canvasRef.current || viewerRef.current) return;
// Используем динамический импорт для обхода проблемы ESM/CommonJS
const initSkinViewer = async () => {
try {
const skinview3d = await import('skinview3d');
if (disposed) return;
// Создаем просмотрщик скина по документации
const viewer = new skinview3d.SkinViewer({
canvas: canvasRef.current,
width,
height,
skin:
skinUrl ||
'https://static.planetminecraft.com/files/resource_media/skin/original-steve-15053860.png',
model: 'auto-detect',
cape: capeUrl || undefined,
});
// Настраиваем вращение
// базовая настройка
viewer.autoRotate = autoRotate;
// Настраиваем анимацию ходьбы
viewer.animation = new skinview3d.WalkingAnimation();
viewer.animation.speed = walkingSpeed;
// анимация ходьбы
const walking = new skinview3d.WalkingAnimation();
walking.speed = walkingSpeed;
viewer.animation = walking;
// Сохраняем экземпляр для очистки
viewerRef.current = viewer;
} catch (error) {
console.error('Ошибка при инициализации skinview3d:', error);
animRef.current = walking;
// выставляем ресурсы сразу
const finalSkin = skinUrl?.trim() ? skinUrl : DEFAULT_SKIN;
await viewer.loadSkin(finalSkin);
if (capeUrl?.trim()) {
await viewer.loadCape(capeUrl);
} else {
viewer.cape = null;
}
} catch (e) {
console.error('Ошибка при инициализации skinview3d:', e);
}
};
initSkinViewer();
init();
// Очистка при размонтировании
return () => {
disposed = true;
if (viewerRef.current) {
viewerRef.current.dispose();
viewerRef.current = null;
animRef.current = null;
}
};
}, [width, height, skinUrl, capeUrl, walkingSpeed, autoRotate]);
// ⚠️ пустой deps — создаём один раз
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<canvas
ref={canvasRef}
width={width}
height={height}
style={{ display: 'block' }}
/>
);
// 2) Обновляем размеры (не пересоздаём viewer)
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
viewer.width = width;
viewer.height = height;
}, [width, height]);
// 3) Обновляем автоповорот
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
viewer.autoRotate = autoRotate;
}, [autoRotate]);
// 4) Обновляем скорость анимации
useEffect(() => {
const walking = animRef.current;
if (!walking) return;
walking.speed = walkingSpeed;
}, [walkingSpeed]);
// 5) Обновляем скин
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
const finalSkin = skinUrl?.trim() ? skinUrl : DEFAULT_SKIN;
// защита от кеша: добавим “bust” только если URL уже имеет query — не обязательно, но помогает
const url = finalSkin.includes('?') ? `${finalSkin}&t=${Date.now()}` : `${finalSkin}?t=${Date.now()}`;
viewer.loadSkin(url).catch((e: any) => console.error('loadSkin error:', e));
}, [skinUrl]);
// 6) Обновляем плащ
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
if (capeUrl?.trim()) {
const url = capeUrl.includes('?') ? `${capeUrl}&t=${Date.now()}` : `${capeUrl}?t=${Date.now()}`;
viewer.loadCape(url).catch((e: any) => console.error('loadCape error:', e));
} else {
viewer.cape = null;
}
}, [capeUrl]);
return <canvas ref={canvasRef} width={width} height={height} style={{ display: 'block' }} />;
}