import React, { useEffect, useMemo, useRef, useState } from 'react'; import styles from './RotatedImage.module.css'; type Mode = 'cover' | 'contain'; type RotatedImageProps = { url: string; rotationDeg: 0 | 90 | 180 | 270; mode: Mode; alt?: string; /** Высота/ширина полностью контролируются родителем. */ style?: React.CSSProperties; /** Прямоугольник видимого контента (contain/cover) внутри контейнера. */ onContentRectChange?: ((rect: { x: number; y: number; w: number; h: number }) => void) | undefined; }; function useElementSize() { const ref = useRef(null); const [size, setSize] = useState<{ w: number; h: number }>({ w: 0, h: 0 }); useEffect(() => { const el = ref.current; if (!el) return; const ro = new ResizeObserver(() => { const r = el.getBoundingClientRect(); setSize({ w: r.width, h: r.height }); }); ro.observe(el); const r = el.getBoundingClientRect(); setSize({ w: r.width, h: r.height }); return () => ro.disconnect(); }, []); return [ref, size] as const; } export function RotatedImage({ url, rotationDeg, mode, alt = '', style, onContentRectChange, }: RotatedImageProps) { const [ref, size] = useElementSize(); const [imgSize, setImgSize] = useState<{ w: number; h: number } | null>(null); useEffect(() => { let cancelled = false; const img = new Image(); img.onload = () => { if (cancelled) return; setImgSize({ w: img.naturalWidth || 1, h: img.naturalHeight || 1 }); }; img.src = url; return () => { cancelled = true; }; }, [url]); const scale = useMemo(() => { if (!imgSize) return 1; if (size.w <= 1 || size.h <= 1) return 1; const rotated = rotationDeg === 90 || rotationDeg === 270; const iw = rotated ? imgSize.h : imgSize.w; const ih = rotated ? imgSize.w : imgSize.h; const sx = size.w / iw; const sy = size.h / ih; return mode === 'cover' ? Math.max(sx, sy) : Math.min(sx, sy); }, [imgSize, mode, rotationDeg, size.h, size.w]); useEffect(() => { if (!onContentRectChange) return; if (!imgSize) return; if (size.w <= 1 || size.h <= 1) return; const rotated = rotationDeg === 90 || rotationDeg === 270; // Bounding-box размеров после rotate(): при 90/270 меняются местами. const bw = (rotated ? imgSize.h : imgSize.w) * scale; const bh = (rotated ? imgSize.w : imgSize.h) * scale; const x = (size.w - bw) / 2; const y = (size.h - bh) / 2; onContentRectChange({ x, y, w: bw, h: bh }); }, [imgSize, mode, onContentRectChange, rotationDeg, scale, size.h, size.w]); const w = imgSize ? imgSize.w * scale : undefined; const h = imgSize ? imgSize.h * scale : undefined; return (
{alt}
); }