I want to make a copy of the profile photo upload area used on Instagram and Upwork that works in the same way. I’ve been trying for a while and the only solution I could find is as follows. I had to use a lot of ifs but I don’t want to do that. Is there a simpler or more logical or easier way or something better that you think is better like this?
I think I need to change the handleMouseMove function, please help !!
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { Dialog, DialogContent, DialogTitle } from "./ui/dialog";
import { Button } from "./ui/button";
import { cn } from "@/lib/utils";
import "react-image-crop/dist/ReactCrop.css";
function EditProfilePhotoDialog({ open, setOpen, profilePic }: { open: boolean; setOpen: React.Dispatch<React.SetStateAction<boolean>>; profilePic: React.Dispatch<React.SetStateAction<string>> }) {
const fileInputRef = useRef<HTMLInputElement | null>(null);
const [scale, setScale] = useState(1);
const [error, setError] = useState("");
const [imgSrc, setImgSrc] = useState<string | null>(null);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const imgRef = useRef<HTMLImageElement | null>(null);
const [position, setPosition] = useState({ x: 0, y: 0 }); // Canvas'ın konumunu takip etmek için state
// Mouse sürükleme olaylarını takip etmek için gerekli state'ler
const [dragging, setDragging] = useState(false);
const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
console.log("render");
const handleImageUpload = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
const img = new window.Image();
img.onload = () => {
setImgSrc(reader.result as string);
const canvas = document.createElement("canvas");
canvasRef.current = canvas;
const container = document.getElementById("canvas-container");
if (container && canvas) {
container.appendChild(canvas);
const orgWidth = img.width;
const orgHeight = img.height;
const scl = 300 / Math.min(orgWidth, orgHeight);
const newWidth = orgWidth * scl;
const newHeight = orgHeight * scl;
canvas.width = newWidth;
canvas.height = newHeight;
canvas.className = `absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 ${dragging ? "cursor-grabbing" : "cursor-grab"}`;
canvas.id = "cnv";
const ctx = canvas.getContext("2d");
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.globalAlpha = 0.3;
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0, newWidth, newHeight);
ctx.restore();
}
}
imgRef.current = img; // Resmi referansla sakla
};
img.src = reader.result as string;
};
reader.readAsDataURL(file);
}
};
useEffect(() => {
const canvas = canvasRef.current;
const container = document.getElementById("canvas-container");
if (canvas && container) {
const containerRect = container.getBoundingClientRect();
const canvasRect = canvas.getBoundingClientRect();
const handleMouseDown = (e: MouseEvent) => {
setDragging(true);
setPosition({ x: canvas.offsetLeft, y: canvas.offsetTop });
setStartPosition({ x: e.clientX, y: e.clientY });
};
const handleMouseMove = (e: MouseEvent) => {
if (!dragging) return;
let newX = position.x;
let newY = position.y;
if (canvasRect.width === 300) {
newX = position.x;
}
if (canvasRect.height === 300) {
newY = position.y;
}
let kilitTop = canvasRect.top <= containerRect.top - 10;
let kilitLeft = canvasRect.left <= containerRect.left - 10;
let kilitBot = canvasRect.bottom >= containerRect.bottom + 10;
let kilitRight = canvasRect.right >= containerRect.right + 10;
if (kilitTop && kilitBot) {
newY = position.y + (e.clientY - startPosition.y);
}
if (kilitLeft && kilitRight) {
newX = position.x + (e.clientX - startPosition.x);
}
if (canvasRect.height !== 300 && !kilitTop && e.clientY - startPosition.y < 0) {
newY = position.y + (e.clientY - startPosition.y);
}
if (canvasRect.height !== 300 && !kilitTop && canvasRect.top > containerRect.top) {
newY = position.y + (containerRect.top - canvasRect.top);
}
if (canvasRect.height !== 300 && !kilitBot && e.clientY - startPosition.y > 0) {
newY = position.y + (e.clientY - startPosition.y);
}
if (canvasRect.height !== 300 && !kilitBot && canvasRect.bottom < containerRect.bottom) {
newY = position.y - (canvasRect.bottom - containerRect.bottom);
}
if (canvasRect.width !== 300 && !kilitLeft && e.clientX - startPosition.x < 0) {
newX = position.x + (e.clientX - startPosition.x);
}
if (canvasRect.width !== 300 && !kilitLeft && canvasRect.left > containerRect.left) {
newX = position.x + (containerRect.left - canvasRect.left);
}
if (canvasRect.width !== 300 && !kilitRight && e.clientX - startPosition.x > 0) {
newX = position.x + (e.clientX - startPosition.x);
}
if (canvasRect.width !== 300 && !kilitRight && canvasRect.right < containerRect.right) {
newX = position.x + (containerRect.right - canvasRect.right);
}
setPosition({ x: newX, y: newY });
setStartPosition({ x: e.clientX, y: e.clientY });
};
const handleMouseUp = () => setDragging(false);
const handleMouseLeave = () => setDragging(false);
canvas.addEventListener("mousedown", handleMouseDown);
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
canvas.addEventListener("mouseleave", handleMouseLeave);
return () => {
if (canvas) {
canvas.removeEventListener("mousedown", handleMouseDown);
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
canvas.removeEventListener("mouseleave", handleMouseLeave);
}
};
}
}, [imgSrc, dragging, position, setPosition]);
useEffect(() => {
const canvas = canvasRef.current;
if (canvas) {
canvas.style.left = `${position.x}px`;
canvas.style.top = `${position.y}px`;
}
}, [position]);
useEffect(() => {
const canvas = canvasRef.current;
if (canvas) canvas.style.cursor = dragging ? "grabbing" : "grab";
}, [dragging]);
useEffect(() => {
const canvas = canvasRef.current;
if (canvas) {
setPosition({ x: position.x, y: position.y });
canvas.style.transform = `scale(${scale})`;
canvas.style.transformOrigin = "center";
}
}, [scale]);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="w-11/12 md:w-[700px] max-w-[700px] min-h-[70vh] font-poppins" aria-describedby={undefined}>
<DialogTitle className="sr-only">Profil fotoğrafını düzenle</DialogTitle>
<div className="flex items-center space-x-3">
<h1 className="tracking-wide">Fotoğrafı düzenle</h1>
</div>
<div className="flex">
<div className="space-y-5 w-full flex flex-col items-center justify-center">
<div className="w-[300px] h-[300px] relative overflow-hidden rounded-full" id="canvas-container">
<label
htmlFor="image-upload"
className={cn(
"w-full h-full rounded-full border-slate-600 border-2 border-dashed flex items-center justify-center overflow-hidden",
imgSrc === null ? "cursor-pointer" : "pointer-events-none"
)}
>
{!imgSrc && <p className="text-xl font-semibold cursor-pointer">Bir resim yükleyin</p>}
</label>
<input ref={fileInputRef} type="file" accept="image/" id="image-upload" className={cn("hidden")} onChange={handleImageUpload} />
</div>
</div>
<div className="hidden sm:block p-5 mt-10">
{imgSrc && (
<input
type="range"
value={scale}
max={2}
min={1}
step={0.1}
onChange={(e) => {
setScale(Number(e.target.value));
}}
/>
)}
<div className="flex items-center justify-center space-x-2">
<div className="w-24 h-24">
<div className="w-full h-full rounded-full bg-gray-200"></div>
</div>
<div className="w-16 h-16">
<div className="w-full h-full rounded-full bg-gray-200"></div>
</div>
<div className="w-12 h-12">
<div className="w-full h-full rounded-full bg-gray-200"></div>
</div>
</div>
</div>
</div>
<div className="w-full flex justify-center sm:justify-end space-x-2">
{imgSrc ? (
<Button variant="ghost">Farklı Resim Yükle</Button>
) : (
<Button variant="ghost" onClick={() => setOpen(false)}>
İptal et
</Button>
)}
<Button variant={"outline"} className="border-green-600">
Resmi uygula
</Button>
</div>
</DialogContent>
</Dialog>
);
}
export default EditProfilePhotoDialog;