feat: 마우스 드래그 및 터치 이벤트 처리 개선

- 캔버스 드래그 시 스크롤 방지 로직 추가
- 마우스/터치 이벤트 핸들러 통합 및 개선
- 이벤트 리스너 등록/제거 로직 최적화
This commit is contained in:
BaekRyang 2025-11-06 09:23:10 +09:00
parent ddcf8b463a
commit d2e83ac9a5
6 changed files with 171 additions and 62 deletions

69
dist/index.js vendored
View File

@ -407,12 +407,14 @@ var useMouseVelocity = (containerRef) => {
mouseStateRef.current.isDragging = false;
}, []);
const handleTouchMove = (0, import_react2.useCallback)((e) => {
e.preventDefault();
if (e.touches.length > 0) {
const touch = e.touches[0];
updatePosition(touch.clientX, touch.clientY);
}
}, [updatePosition]);
const handleTouchStart = (0, import_react2.useCallback)((e) => {
e.preventDefault();
mouseStateRef.current.isDragging = true;
mouseStateRef.current.isHovering = true;
if (e.touches.length > 0) {
@ -437,8 +439,8 @@ var useMouseVelocity = (containerRef) => {
container.addEventListener("mouseleave", handleMouseLeave);
container.addEventListener("mousedown", handleMouseDown);
window.addEventListener("mouseup", handleMouseUp);
container.addEventListener("touchmove", handleTouchMove, { passive: true });
container.addEventListener("touchstart", handleTouchStart, { passive: true });
container.addEventListener("touchmove", handleTouchMove, { passive: false });
container.addEventListener("touchstart", handleTouchStart, { passive: false });
container.addEventListener("touchend", handleTouchEnd);
container.addEventListener("touchcancel", handleTouchEnd);
return () => {
@ -1100,7 +1102,7 @@ var EditorCanvas = ({
}
return inside;
}, []);
const handleMouseDown = (0, import_react6.useCallback)(
const handlePointDown = (0, import_react6.useCallback)(
(pointIndex) => (e) => {
e.preventDefault();
e.stopPropagation();
@ -1108,12 +1110,21 @@ var EditorCanvas = ({
},
[onStartDragging]
);
const handleCanvasMouseDown = (0, import_react6.useCallback)(
const handleCanvasDown = (0, import_react6.useCallback)(
(e) => {
if (!showEditor || !selectedArea || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
let clientX, clientY;
if ("touches" in e) {
if (e.touches.length === 0) return;
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const x = (clientX - rect.left) / rect.width;
const y = (clientY - rect.top) / rect.height;
const clickPoint = { x, y };
if (isPointInPolygon2(clickPoint, selectedArea.basePoints)) {
setIsDraggingArea(true);
@ -1123,12 +1134,24 @@ var EditorCanvas = ({
},
[showEditor, selectedArea, isPointInPolygon2]
);
const handleMouseMove = (0, import_react6.useCallback)(
const handleMove = (0, import_react6.useCallback)(
(e) => {
if (!showEditor || !selectedArea || !containerRef.current) return;
if ("touches" in e && (draggingPointIndex !== null || isDraggingArea)) {
e.preventDefault();
}
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
let clientX, clientY;
if ("touches" in e) {
if (e.touches.length === 0) return;
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const x = (clientX - rect.left) / rect.width;
const y = (clientY - rect.top) / rect.height;
if (draggingPointIndex !== null) {
const clampedX = Math.max(0, Math.min(1, x));
const clampedY = Math.max(0, Math.min(1, y));
@ -1146,7 +1169,7 @@ var EditorCanvas = ({
},
[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
);
const handleMouseUp = (0, import_react6.useCallback)(() => {
const handleUp = (0, import_react6.useCallback)(() => {
if (draggingPointIndex !== null) {
onStopDragging();
}
@ -1157,10 +1180,16 @@ var EditorCanvas = ({
}, [draggingPointIndex, isDraggingArea, onStopDragging]);
(0, import_react6.useEffect)(() => {
if (draggingPointIndex !== null || isDraggingArea) {
window.addEventListener("mouseup", handleMouseUp);
return () => window.removeEventListener("mouseup", handleMouseUp);
window.addEventListener("mouseup", handleUp);
window.addEventListener("touchend", handleUp);
window.addEventListener("touchcancel", handleUp);
return () => {
window.removeEventListener("mouseup", handleUp);
window.removeEventListener("touchend", handleUp);
window.removeEventListener("touchcancel", handleUp);
};
}
}, [draggingPointIndex, isDraggingArea, handleMouseUp]);
}, [draggingPointIndex, isDraggingArea, handleUp]);
const uvToPixel = (u, v, points, canvasWidth, canvasHeight) => {
const [p0, p1, p2, p3] = points;
const leftX = p0.x * (1 - u) + p1.x * u;
@ -1240,11 +1269,14 @@ var EditorCanvas = ({
height,
position: "relative",
cursor: showEditor ? getCursorStyle() : "default",
pointerEvents: showEditor ? "auto" : "none"
// 에디터 숨김 시 포인터 이벤트 비활성화
pointerEvents: showEditor ? "auto" : "none",
touchAction: "none"
// 터치 시 모든 브라우저 동작 비활성화 (스크롤, 줌 등)
},
onMouseDown: showEditor ? handleCanvasMouseDown : void 0,
onMouseMove: showEditor ? handleMouseMove : void 0,
onMouseDown: showEditor ? handleCanvasDown : void 0,
onMouseMove: showEditor ? handleMove : void 0,
onTouchStart: showEditor ? handleCanvasDown : void 0,
onTouchMove: showEditor ? handleMove : void 0,
children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas }),
showEditor && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@ -1320,7 +1352,8 @@ var EditorCanvas = ({
pointerEvents: "auto",
boxShadow: "0 2px 4px rgba(0,0,0,0.3)"
},
onMouseDown: handleMouseDown(index),
onMouseDown: handlePointDown(index),
onTouchStart: handlePointDown(index),
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
"div",
{

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

69
dist/index.mjs vendored
View File

@ -358,12 +358,14 @@ var useMouseVelocity = (containerRef) => {
mouseStateRef.current.isDragging = false;
}, []);
const handleTouchMove = useCallback((e) => {
e.preventDefault();
if (e.touches.length > 0) {
const touch = e.touches[0];
updatePosition(touch.clientX, touch.clientY);
}
}, [updatePosition]);
const handleTouchStart = useCallback((e) => {
e.preventDefault();
mouseStateRef.current.isDragging = true;
mouseStateRef.current.isHovering = true;
if (e.touches.length > 0) {
@ -388,8 +390,8 @@ var useMouseVelocity = (containerRef) => {
container.addEventListener("mouseleave", handleMouseLeave);
container.addEventListener("mousedown", handleMouseDown);
window.addEventListener("mouseup", handleMouseUp);
container.addEventListener("touchmove", handleTouchMove, { passive: true });
container.addEventListener("touchstart", handleTouchStart, { passive: true });
container.addEventListener("touchmove", handleTouchMove, { passive: false });
container.addEventListener("touchstart", handleTouchStart, { passive: false });
container.addEventListener("touchend", handleTouchEnd);
container.addEventListener("touchcancel", handleTouchEnd);
return () => {
@ -1051,7 +1053,7 @@ var EditorCanvas = ({
}
return inside;
}, []);
const handleMouseDown = useCallback5(
const handlePointDown = useCallback5(
(pointIndex) => (e) => {
e.preventDefault();
e.stopPropagation();
@ -1059,12 +1061,21 @@ var EditorCanvas = ({
},
[onStartDragging]
);
const handleCanvasMouseDown = useCallback5(
const handleCanvasDown = useCallback5(
(e) => {
if (!showEditor || !selectedArea || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
let clientX, clientY;
if ("touches" in e) {
if (e.touches.length === 0) return;
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const x = (clientX - rect.left) / rect.width;
const y = (clientY - rect.top) / rect.height;
const clickPoint = { x, y };
if (isPointInPolygon2(clickPoint, selectedArea.basePoints)) {
setIsDraggingArea(true);
@ -1074,12 +1085,24 @@ var EditorCanvas = ({
},
[showEditor, selectedArea, isPointInPolygon2]
);
const handleMouseMove = useCallback5(
const handleMove = useCallback5(
(e) => {
if (!showEditor || !selectedArea || !containerRef.current) return;
if ("touches" in e && (draggingPointIndex !== null || isDraggingArea)) {
e.preventDefault();
}
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
let clientX, clientY;
if ("touches" in e) {
if (e.touches.length === 0) return;
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const x = (clientX - rect.left) / rect.width;
const y = (clientY - rect.top) / rect.height;
if (draggingPointIndex !== null) {
const clampedX = Math.max(0, Math.min(1, x));
const clampedY = Math.max(0, Math.min(1, y));
@ -1097,7 +1120,7 @@ var EditorCanvas = ({
},
[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
);
const handleMouseUp = useCallback5(() => {
const handleUp = useCallback5(() => {
if (draggingPointIndex !== null) {
onStopDragging();
}
@ -1108,10 +1131,16 @@ var EditorCanvas = ({
}, [draggingPointIndex, isDraggingArea, onStopDragging]);
useEffect4(() => {
if (draggingPointIndex !== null || isDraggingArea) {
window.addEventListener("mouseup", handleMouseUp);
return () => window.removeEventListener("mouseup", handleMouseUp);
window.addEventListener("mouseup", handleUp);
window.addEventListener("touchend", handleUp);
window.addEventListener("touchcancel", handleUp);
return () => {
window.removeEventListener("mouseup", handleUp);
window.removeEventListener("touchend", handleUp);
window.removeEventListener("touchcancel", handleUp);
};
}
}, [draggingPointIndex, isDraggingArea, handleMouseUp]);
}, [draggingPointIndex, isDraggingArea, handleUp]);
const uvToPixel = (u, v, points, canvasWidth, canvasHeight) => {
const [p0, p1, p2, p3] = points;
const leftX = p0.x * (1 - u) + p1.x * u;
@ -1191,11 +1220,14 @@ var EditorCanvas = ({
height,
position: "relative",
cursor: showEditor ? getCursorStyle() : "default",
pointerEvents: showEditor ? "auto" : "none"
// 에디터 숨김 시 포인터 이벤트 비활성화
pointerEvents: showEditor ? "auto" : "none",
touchAction: "none"
// 터치 시 모든 브라우저 동작 비활성화 (스크롤, 줌 등)
},
onMouseDown: showEditor ? handleCanvasMouseDown : void 0,
onMouseMove: showEditor ? handleMouseMove : void 0,
onMouseDown: showEditor ? handleCanvasDown : void 0,
onMouseMove: showEditor ? handleMove : void 0,
onTouchStart: showEditor ? handleCanvasDown : void 0,
onTouchMove: showEditor ? handleMove : void 0,
children: [
/* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas }),
showEditor && /* @__PURE__ */ jsx2(
@ -1271,7 +1303,8 @@ var EditorCanvas = ({
pointerEvents: "auto",
boxShadow: "0 2px 4px rgba(0,0,0,0.3)"
},
onMouseDown: handleMouseDown(index),
onMouseDown: handlePointDown(index),
onTouchStart: handlePointDown(index),
children: /* @__PURE__ */ jsxs(
"div",
{

2
dist/index.mjs.map vendored

File diff suppressed because one or more lines are too long

View File

@ -83,9 +83,9 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
return inside;
}, []);
// 마우스 이벤트 핸들러
const handleMouseDown = useCallback(
(pointIndex: number) => (e: React.MouseEvent) => {
// 포인트 핸들 클릭/터치 핸들러
const handlePointDown = useCallback(
(pointIndex: number) => (e: React.MouseEvent | React.TouchEvent) => {
e.preventDefault();
e.stopPropagation();
onStartDragging(pointIndex);
@ -93,15 +93,27 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
[onStartDragging]
);
// 캔버스 클릭 (사각형 내부 클릭 감지)
const handleCanvasMouseDown = useCallback(
(e: React.MouseEvent) => {
// 캔버스 다운 (마우스/터치 공통)
const handleCanvasDown = useCallback(
(e: React.MouseEvent | React.TouchEvent) => {
// 에디터가 숨겨진 상태면 동작하지 않음
if (!showEditor || !selectedArea || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
// 마우스 또는 터치 좌표 추출
let clientX: number, clientY: number;
if ('touches' in e) {
if (e.touches.length === 0) return;
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const x = (clientX - rect.left) / rect.width;
const y = (clientY - rect.top) / rect.height;
const clickPoint = { x, y };
// 사각형 내부를 클릭했는지 확인
@ -114,14 +126,32 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
[showEditor, selectedArea, isPointInPolygon]
);
const handleMouseMove = useCallback(
(e: React.MouseEvent) => {
// 이동 (마우스/터치 공통)
const handleMove = useCallback(
(e: React.MouseEvent | React.TouchEvent) => {
// 에디터가 숨겨진 상태면 동작하지 않음
if (!showEditor || !selectedArea || !containerRef.current) return;
// 터치 이벤트면 스크롤 방지
if ('touches' in e && (draggingPointIndex !== null || isDraggingArea)) {
e.preventDefault();
}
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
// 마우스 또는 터치 좌표 추출
let clientX: number, clientY: number;
if ('touches' in e) {
if (e.touches.length === 0) return;
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const x = (clientX - rect.left) / rect.width;
const y = (clientY - rect.top) / rect.height;
// 포인트 드래그 중
if (draggingPointIndex !== null) {
@ -147,7 +177,8 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]
);
const handleMouseUp = useCallback(() => {
// 업 (마우스/터치 공통)
const handleUp = useCallback(() => {
if (draggingPointIndex !== null) {
onStopDragging();
}
@ -157,13 +188,19 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
}
}, [draggingPointIndex, isDraggingArea, onStopDragging]);
// 전역 마우스 업 이벤트
// 전역 업 이벤트 (마우스/터치)
useEffect(() => {
if (draggingPointIndex !== null || isDraggingArea) {
window.addEventListener('mouseup', handleMouseUp);
return () => window.removeEventListener('mouseup', handleMouseUp);
window.addEventListener('mouseup', handleUp);
window.addEventListener('touchend', handleUp);
window.addEventListener('touchcancel', handleUp);
return () => {
window.removeEventListener('mouseup', handleUp);
window.removeEventListener('touchend', handleUp);
window.removeEventListener('touchcancel', handleUp);
};
}
}, [draggingPointIndex, isDraggingArea, handleMouseUp]);
}, [draggingPointIndex, isDraggingArea, handleUp]);
// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)
const uvToPixel = (
@ -280,10 +317,13 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
height,
position: 'relative',
cursor: showEditor ? getCursorStyle() : 'default',
pointerEvents: showEditor ? 'auto' : 'none', // 에디터 숨김 시 포인터 이벤트 비활성화
pointerEvents: showEditor ? 'auto' : 'none',
touchAction: 'none', // 터치 시 모든 브라우저 동작 비활성화 (스크롤, 줌 등)
}}
onMouseDown={showEditor ? handleCanvasMouseDown : undefined}
onMouseMove={showEditor ? handleMouseMove : undefined}
onMouseDown={showEditor ? handleCanvasDown : undefined}
onMouseMove={showEditor ? handleMove : undefined}
onTouchStart={showEditor ? handleCanvasDown : undefined}
onTouchMove={showEditor ? handleMove : undefined}
>
{/* ImageDistortion 컴포넌트 */}
<ImageDistortion imageSrc={imageSrc} areas={areas}/>
@ -371,7 +411,8 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
pointerEvents: 'auto',
boxShadow: '0 2px 4px rgba(0,0,0,0.3)',
}}
onMouseDown={handleMouseDown(index)}
onMouseDown={handlePointDown(index)}
onTouchStart={handlePointDown(index)}
>
<div
style={{

View File

@ -122,6 +122,7 @@ export const useMouseVelocity = (containerRef: React.RefObject<HTMLElement | nul
*
*/
const handleTouchMove = useCallback((e: TouchEvent) => {
e.preventDefault(); // 스크롤 방지
if (e.touches.length > 0) {
const touch = e.touches[0];
updatePosition(touch.clientX, touch.clientY);
@ -132,6 +133,7 @@ export const useMouseVelocity = (containerRef: React.RefObject<HTMLElement | nul
*
*/
const handleTouchStart = useCallback((e: TouchEvent) => {
e.preventDefault(); // 스크롤 방지
mouseStateRef.current.isDragging = true;
mouseStateRef.current.isHovering = true;
if (e.touches.length > 0) {
@ -167,9 +169,9 @@ export const useMouseVelocity = (containerRef: React.RefObject<HTMLElement | nul
container.addEventListener('mousedown', handleMouseDown);
window.addEventListener('mouseup', handleMouseUp);
// 터치 이벤트
container.addEventListener('touchmove', handleTouchMove, { passive: true });
container.addEventListener('touchstart', handleTouchStart, { passive: true });
// 터치 이벤트 (passive: false로 스크롤 방지 가능)
container.addEventListener('touchmove', handleTouchMove, { passive: false });
container.addEventListener('touchstart', handleTouchStart, { passive: false });
container.addEventListener('touchend', handleTouchEnd);
container.addEventListener('touchcancel', handleTouchEnd);