import React, { useState, useRef, useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import styles from './CropPhoto.module.css'; const CropPhoto: React.FC = () => { const location = useLocation(); const navigate = useNavigate(); const imageRef = useRef(null); const wrapperRef = useRef(null); const viewportRef = useRef(null); const [imageUrl, setImageUrl] = useState(null); const [scale, setScale] = useState(1); const [minScale, setMinScale] = useState(1); const [position, setPosition] = useState({ x: 0, y: 0 }); const [isDragging, setIsDragging] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); const [initialTouch, setInitialTouch] = useState<{ distance: number; scale: number } | null>(null); // Эффект для создания и очистки blob URL useEffect(() => { const file = (location.state as any)?.file; if (!file) { navigate('/'); return; } // Создаем URL только если его еще нет if (!imageUrl) { const url = URL.createObjectURL(file); setImageUrl(url); } // Очищаем URL при размонтировании компонента return () => { if (imageUrl) { URL.revokeObjectURL(imageUrl); setImageUrl(null); } }; }, [location.state, navigate, imageUrl]); // Отдельный эффект для обработки загруженного изображения useEffect(() => { if (!imageUrl || !viewportRef.current) return; const img = new Image(); img.onload = () => { const viewport = viewportRef.current; if (!viewport) return; const frame = viewport.querySelector(`.${styles.frame}`); if (!frame) return; const viewportRect = viewport.getBoundingClientRect(); const frameRect = frame.getBoundingClientRect(); // Используем размеры рамки обрезки const frameWidth = frameRect.width; const frameHeight = frameRect.height; // Вычисляем масштаб, чтобы изображение полностью покрывало область обрезки const scaleX = frameWidth / img.naturalWidth; const scaleY = frameHeight / img.naturalHeight; const baseScale = Math.max(scaleX, scaleY); setMinScale(baseScale); setScale(baseScale); // Центрируем изображение относительно рамки const scaledWidth = img.naturalWidth * baseScale; const scaledHeight = img.naturalHeight * baseScale; // Учитываем отступы от рамки до viewport const frameLeft = frameRect.left - viewportRect.left; const frameTop = frameRect.top - viewportRect.top; setPosition({ x: frameLeft + (frameWidth - scaledWidth) / 2, y: frameTop + (frameHeight - scaledHeight) / 2 }); }; img.onerror = () => { // В случае ошибки загрузки изображения console.error('Ошибка загрузки изображения'); navigate('/'); }; img.src = imageUrl; }, [imageUrl, navigate]); const handleStart = (e: React.MouseEvent | React.TouchEvent) => { if ('touches' in e) { if (e.touches.length === 2) { const distance = getDistance(e.touches); setInitialTouch({ distance, scale }); setIsDragging(false); } else { const touch = e.touches[0]; setIsDragging(true); setDragStart({ x: touch.clientX - position.x, y: touch.clientY - position.y }); } } else { setIsDragging(true); setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y }); } }; const handleMove = (e: React.MouseEvent | React.TouchEvent) => { if ('touches' in e) { if (e.touches.length === 2 && initialTouch) { // Масштабирование двумя пальцами const distance = getDistance(e.touches); const newScale = Math.min( Math.max( (initialTouch.scale * distance) / initialTouch.distance, minScale ), minScale * 3 ); handleScale(newScale); } else if (isDragging) { const touch = e.touches[0]; handleDrag(touch.clientX, touch.clientY); } } else if (isDragging) { handleDrag(e.clientX, e.clientY); } }; const handleEnd = () => { setIsDragging(false); setInitialTouch(null); }; const getDistance = (touches: React.TouchList) => { const dx = touches[0].clientX - touches[1].clientX; const dy = touches[0].clientY - touches[1].clientY; return Math.sqrt(dx * dx + dy * dy); }; const handleDrag = (clientX: number, clientY: number) => { if (!viewportRef.current || !imageRef.current) return; const viewport = viewportRef.current; const frame = viewport.querySelector(`.${styles.frame}`); if (!frame) return; const viewportRect = viewport.getBoundingClientRect(); const frameRect = frame.getBoundingClientRect(); // Получаем позицию рамки относительно viewport const frameLeft = frameRect.left - viewportRect.left; const frameTop = frameRect.top - viewportRect.top; // Вычисляем размеры масштабированного изображения const scaledWidth = imageRef.current.naturalWidth * scale; const scaledHeight = imageRef.current.naturalHeight * scale; // Вычисляем новую позицию const newX = clientX - dragStart.x; const newY = clientY - dragStart.y; // Ограничиваем перемещение const minX = frameLeft - (scaledWidth - frameRect.width); const maxX = frameLeft; const minY = frameTop - (scaledHeight - frameRect.height); const maxY = frameTop; setPosition({ x: Math.max(minX, Math.min(maxX, newX)), y: Math.max(minY, Math.min(maxY, newY)) }); }; const handleScale = (newScale: number) => { if (!viewportRef.current || !imageRef.current) return; const viewport = viewportRef.current; const frame = viewport.querySelector(`.${styles.frame}`); if (!frame) return; const viewportRect = viewport.getBoundingClientRect(); const frameRect = frame.getBoundingClientRect(); // Получаем позицию рамки относительно viewport const frameLeft = frameRect.left - viewportRect.left; const frameTop = frameRect.top - viewportRect.top; // Вычисляем центр рамки const frameCenterX = frameLeft + frameRect.width / 2; const frameCenterY = frameTop + frameRect.height / 2; // Вычисляем текущий центр изображения const currentCenterX = position.x + (imageRef.current.naturalWidth * scale) / 2; const currentCenterY = position.y + (imageRef.current.naturalHeight * scale) / 2; // Вычисляем смещение для сохранения центра const dx = (frameCenterX - currentCenterX) * (newScale / scale - 1); const dy = (frameCenterY - currentCenterY) * (newScale / scale - 1); // Вычисляем новые размеры изображения const newScaledWidth = imageRef.current.naturalWidth * newScale; const newScaledHeight = imageRef.current.naturalHeight * newScale; // Вычисляем новую позицию let newX = position.x + dx; let newY = position.y + dy; // Ограничиваем позицию const minX = frameLeft - (newScaledWidth - frameRect.width); const maxX = frameLeft; const minY = frameTop - (newScaledHeight - frameRect.height); const maxY = frameTop; setScale(newScale); setPosition({ x: Math.max(minX, Math.min(maxX, newX)), y: Math.max(minY, Math.min(maxY, newY)) }); }; const handleZoom = (e: React.ChangeEvent) => { handleScale(parseFloat(e.target.value)); }; const createPreview = () => { if (!imageRef.current || !viewportRef.current) return null; const canvas = document.createElement('canvas'); canvas.width = 768; canvas.height = 768; const ctx = canvas.getContext('2d'); if (!ctx) return null; const viewport = viewportRef.current; const frame = viewport.querySelector(`.${styles.frame}`); if (!frame) return null; const frameRect = frame.getBoundingClientRect(); const image = imageRef.current; ctx.drawImage( image, -position.x / scale, -position.y / scale, frameRect.width / scale, frameRect.height / scale, 0, 0, 768, 768 ); return canvas.toDataURL('image/jpeg', 0.95); }; const handleConfirm = () => { const previewUrl = createPreview(); if (previewUrl) { // Передаем не только URL, но и base64 данные // Убираем префикс data:image/jpeg;base64, оставляем только данные const imageData = previewUrl.split(',')[1]; // Сохраняем данные в localStorage для сохранения между сеансами навигации localStorage.setItem('stickerPreviewUrl', previewUrl); localStorage.setItem('stickerImageData', imageData); navigate('/', { state: { previewUrl, imageData } }); } }; return (

Обрезка фото

e.preventDefault()} > {imageUrl && (
Crop
)}
Отрегулируйте масштаб и положение фото
); }; export default CropPhoto;