优化放大镜工具的状态管理,增加放大镜切换时清除选择区域的逻辑,改进触摸和鼠标拖拽事件处理,确保用户交互流畅性和页面滚动控制,提升整体用户体验。

This commit is contained in:
zihanjian
2025-06-06 15:10:13 +08:00
parent fe6f3f1129
commit 711836f85a
4 changed files with 150 additions and 32 deletions

View File

@@ -174,7 +174,13 @@ export default function Home() {
// 放大镜切换处理函数
const handleToggleMagnifier = () => {
setIsMagnifierActive(!isMagnifierActive);
const newActiveState = !isMagnifierActive;
setIsMagnifierActive(newActiveState);
// 如果关闭放大镜,清除选择区域,重新开始
if (!newActiveState) {
setMagnifierSelectionArea(null);
}
};
// 放大镜像素编辑处理函数
@@ -2141,6 +2147,7 @@ export default function Home() {
});
setHighlightColorKey(null);
setIsMagnifierActive(false);
setMagnifierSelectionArea(null);
}}
onToggleMagnifier={handleToggleMagnifier}
isMagnifierActive={isMagnifierActive}

View File

@@ -85,10 +85,14 @@ const FloatingColorPalette: React.FC<FloatingColorPaletteProps> = ({
};
const handleMouseMove = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
handleMove(e.clientX, e.clientY);
};
const handleTouchMove = (e: TouchEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.touches.length > 0) {
handleMove(e.touches[0].clientX, e.touches[0].clientY);
}
@@ -96,9 +100,14 @@ const FloatingColorPalette: React.FC<FloatingColorPaletteProps> = ({
const handleEnd = () => {
setIsDragging(false);
// 恢复页面滚动
document.body.style.overflow = '';
};
if (isDragging) {
// 阻止页面滚动
document.body.style.overflow = 'hidden';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleEnd);
document.addEventListener('touchmove', handleTouchMove, { passive: false });
@@ -109,6 +118,8 @@ const FloatingColorPalette: React.FC<FloatingColorPaletteProps> = ({
document.removeEventListener('mouseup', handleEnd);
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleEnd);
// 清理时恢复滚动
document.body.style.overflow = '';
};
}
}, [isDragging, dragOffset]);

View File

@@ -68,14 +68,24 @@ const MagnifierSelectionOverlay: React.FC<MagnifierSelectionOverlayProps> = ({
// 不再强制恢复滚动位置,让浏览器保持自然状态
}, [preventScrolling]);
// 获取画布相对坐标
// 获取画布相对坐标 - 优化移动设备支持
const getCanvasCoordinates = useCallback((clientX: number, clientY: number) => {
if (!canvasRef.current) return null;
const rect = canvasRef.current.getBoundingClientRect();
const canvas = canvasRef.current;
const rect = canvas.getBoundingClientRect();
// 考虑设备像素比和画布缩放
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
// 计算相对于画布的坐标
const x = (clientX - rect.left) * scaleX;
const y = (clientY - rect.top) * scaleY;
return {
x: clientX - rect.left,
y: clientY - rect.top
x: Math.max(0, Math.min(canvas.width, x)),
y: Math.max(0, Math.min(canvas.height, y))
};
}, [canvasRef]);
@@ -150,7 +160,13 @@ const MagnifierSelectionOverlay: React.FC<MagnifierSelectionOverlayProps> = ({
const handleTouchStart = useCallback((event: React.TouchEvent) => {
if (!isActive) return;
// 先阻止默认行为,避免滚动干扰
event.preventDefault();
event.stopPropagation();
const touch = event.touches[0];
if (!touch) return;
const coords = getCanvasCoordinates(touch.clientX, touch.clientY);
if (!coords) return;
@@ -158,18 +174,22 @@ const MagnifierSelectionOverlay: React.FC<MagnifierSelectionOverlayProps> = ({
setSelectionStart(coords);
setSelectionEnd(coords);
disableScroll(); // 禁用滚动
event.preventDefault();
}, [isActive, getCanvasCoordinates, disableScroll]);
const handleTouchMove = useCallback((event: TouchEvent) => {
if (!isSelecting || !selectionStart) return;
// 先阻止默认行为
event.preventDefault();
event.stopPropagation();
const touch = event.touches[0];
if (!touch) return;
const coords = getCanvasCoordinates(touch.clientX, touch.clientY);
if (!coords) return;
setSelectionEnd(coords);
event.preventDefault();
}, [isSelecting, selectionStart, getCanvasCoordinates]);
const handleTouchEnd = useCallback(() => {
@@ -215,11 +235,22 @@ const MagnifierSelectionOverlay: React.FC<MagnifierSelectionOverlayProps> = ({
const getSelectionStyle = useCallback(() => {
if (!selectionStart || !selectionEnd || !canvasRef.current) return {};
const rect = canvasRef.current.getBoundingClientRect();
const minX = Math.min(selectionStart.x, selectionEnd.x);
const minY = Math.min(selectionStart.y, selectionEnd.y);
const maxX = Math.max(selectionStart.x, selectionEnd.x);
const maxY = Math.max(selectionStart.y, selectionEnd.y);
const canvas = canvasRef.current;
const rect = canvas.getBoundingClientRect();
// 将画布坐标转换回屏幕坐标
const scaleX = rect.width / canvas.width;
const scaleY = rect.height / canvas.height;
const screenStartX = selectionStart.x * scaleX;
const screenStartY = selectionStart.y * scaleY;
const screenEndX = selectionEnd.x * scaleX;
const screenEndY = selectionEnd.y * scaleY;
const minX = Math.min(screenStartX, screenEndX);
const minY = Math.min(screenStartY, screenEndY);
const maxX = Math.max(screenStartX, screenEndX);
const maxY = Math.max(screenStartY, screenEndY);
return {
left: rect.left + minX,

View File

@@ -32,8 +32,15 @@ const MagnifierTool: React.FC<MagnifierToolProps> = ({
selectionArea,
onClearSelection
}) => {
const [magnifierPosition, setMagnifierPosition] = useState<{ x: number; y: number }>({ x: 50, y: 50 });
// 计算初始位置,确保在屏幕中央
const getInitialPosition = () => ({
x: Math.max(50, (window.innerWidth - 400) / 2),
y: Math.max(50, (window.innerHeight - 400) / 2)
});
const [magnifierPosition, setMagnifierPosition] = useState<{ x: number; y: number }>(getInitialPosition);
const [isDragging, setIsDragging] = useState<boolean>(false);
const [dragOffset, setDragOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0 });
const magnifierRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -62,14 +69,9 @@ const MagnifierTool: React.FC<MagnifierToolProps> = ({
canvas.width = width * magnifiedCellSize;
canvas.height = height * magnifiedCellSize;
// 设置画布的显示尺寸,确保不会太大
const maxDisplayWidth = 400;
const maxDisplayHeight = 400;
const displayWidth = Math.min(canvas.width, maxDisplayWidth);
const displayHeight = Math.min(canvas.height, maxDisplayHeight);
canvas.style.width = `${displayWidth}px`;
canvas.style.height = `${displayHeight}px`;
// 保持真实尺寸,不压缩
canvas.style.width = `${canvas.width}px`;
canvas.style.height = `${canvas.height}px`;
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -141,7 +143,7 @@ const MagnifierTool: React.FC<MagnifierToolProps> = ({
}
}, [selectionArea, mappedPixelData, selectedColor, onPixelEdit]);
// 处理拖拽移动
// 处理拖拽移动 - 鼠标事件
const handleTitleBarMouseDown = useCallback((event: React.MouseEvent) => {
// 只有点击在标题栏区域且不是按钮时才开始拖拽
const target = event.target as HTMLElement;
@@ -149,33 +151,101 @@ const MagnifierTool: React.FC<MagnifierToolProps> = ({
return; // 点击按钮时不拖拽
}
if (magnifierRef.current) {
const rect = magnifierRef.current.getBoundingClientRect();
// 记录鼠标相对于窗口左上角的偏移
setDragOffset({
x: event.clientX - rect.left,
y: event.clientY - rect.top
});
}
setIsDragging(true);
// 阻止页面滚动
document.body.style.overflow = 'hidden';
event.preventDefault();
}, []);
// 处理拖拽移动 - 触摸事件
const handleTitleBarTouchStart = useCallback((event: React.TouchEvent) => {
// 只有点击在标题栏区域且不是按钮时才开始拖拽
const target = event.target as HTMLElement;
if (target.tagName === 'BUTTON' || target.closest('button')) {
return; // 点击按钮时不拖拽
}
const touch = event.touches[0];
if (!touch) return;
if (magnifierRef.current) {
const rect = magnifierRef.current.getBoundingClientRect();
// 记录触摸相对于窗口左上角的偏移
setDragOffset({
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
});
}
setIsDragging(true);
// 阻止页面滚动
document.body.style.overflow = 'hidden';
event.preventDefault();
}, []);
const handleMouseMove = useCallback((event: MouseEvent) => {
if (isDragging && magnifierRef.current) {
const rect = magnifierRef.current.getBoundingClientRect();
const newX = Math.max(0, Math.min(window.innerWidth - rect.width, event.clientX - rect.width / 2));
const newY = Math.max(0, Math.min(window.innerHeight - rect.height, event.clientY - rect.height / 2));
if (isDragging) {
event.preventDefault();
event.stopPropagation();
// 计算新位置,保持鼠标相对于窗口的偏移不变,不限制边界
const newX = event.clientX - dragOffset.x;
const newY = event.clientY - dragOffset.y;
setMagnifierPosition({ x: newX, y: newY });
}
}, [isDragging]);
}, [isDragging, dragOffset]);
const handleTouchMove = useCallback((event: TouchEvent) => {
if (isDragging) {
event.preventDefault();
event.stopPropagation();
const touch = event.touches[0];
if (!touch) return;
// 计算新位置,保持触摸相对于窗口的偏移不变,不限制边界
const newX = touch.clientX - dragOffset.x;
const newY = touch.clientY - dragOffset.y;
setMagnifierPosition({ x: newX, y: newY });
}
}, [isDragging, dragOffset]);
const handleMouseUp = useCallback(() => {
setIsDragging(false);
// 恢复页面滚动
document.body.style.overflow = '';
}, []);
const handleTouchEnd = useCallback(() => {
setIsDragging(false);
// 恢复页面滚动
document.body.style.overflow = '';
}, []);
useEffect(() => {
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('touchmove', handleTouchMove, { passive: false });
document.addEventListener('touchend', handleTouchEnd);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
// 清理时恢复滚动
document.body.style.overflow = '';
};
}
}, [isDragging, handleMouseMove, handleMouseUp]);
}, [isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
// 重新渲染放大视图
useEffect(() => {
@@ -205,15 +275,14 @@ const MagnifierTool: React.FC<MagnifierToolProps> = ({
className="fixed bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-600 z-50 select-none"
style={{
left: magnifierPosition.x,
top: magnifierPosition.y,
maxWidth: '500px',
maxHeight: '500px'
top: magnifierPosition.y
}}
>
{/* 标题栏 */}
<div
className="flex items-center justify-between p-3 bg-gradient-to-r from-green-500 to-teal-500 text-white rounded-t-xl cursor-move"
onMouseDown={handleTitleBarMouseDown}
onTouchStart={handleTitleBarTouchStart}
>
<div className="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -249,7 +318,7 @@ const MagnifierTool: React.FC<MagnifierToolProps> = ({
{/* 放大视图内容 */}
<div className="p-3">
<div className="border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden">
<div className="border border-gray-300 dark:border-gray-600 rounded-lg overflow-auto max-h-96">
<canvas
ref={canvasRef}
onClick={handleMagnifiedClick}