Add draggable preview and keep 30 histories
This commit is contained in:
+4
-3
@@ -76,9 +76,10 @@ input[type="range"] { accent-color: var(--green); }
|
||||
.zoom-control button { width: 36px; height: 36px; border: 1px solid var(--line); background: white; cursor: pointer; font-size: 20px; }
|
||||
.zoom-control input { width: 95px; }
|
||||
.zoom-control output { width: 38px; color: var(--muted); font-size: 11px; text-align: right; }
|
||||
.canvas-stage { min-height: 610px; flex: 1; overflow: auto; display: grid; place-items: center; padding: 36px; touch-action: pan-x pan-y; }
|
||||
.canvas-wrap { width: max-content; line-height: 0; position: relative; border: 1px solid var(--ink); box-shadow: 10px 10px 0 rgba(32,39,36,.13); background: white; }
|
||||
.canvas-wrap canvas { cursor: crosshair; image-rendering: pixelated; }
|
||||
.canvas-stage { min-height: 610px; flex: 1; overflow: auto; display: block; padding: 36px; cursor: grab; touch-action: none; overscroll-behavior: contain; user-select: none; }
|
||||
.canvas-stage.is-dragging { cursor: grabbing; }
|
||||
.canvas-wrap { width: max-content; line-height: 0; position: relative; margin: auto; border: 1px solid var(--ink); box-shadow: 10px 10px 0 rgba(32,39,36,.13); background: white; }
|
||||
.canvas-wrap canvas { cursor: inherit; image-rendering: pixelated; }
|
||||
.pixel-tooltip { position: absolute; z-index: 5; width: max-content; max-width: 260px; padding: 9px 11px; display: grid; grid-template-columns: 14px auto; align-items: center; gap: 3px 8px; color: white; background: rgba(22, 30, 27, .94); border: 1px solid rgba(255,255,255,.2); box-shadow: 4px 4px 0 rgba(0,0,0,.2); border-radius: 3px; line-height: 1.35; pointer-events: none; }
|
||||
.tooltip-swatch { width: 14px; height: 14px; grid-row: 1 / 3; border-radius: 50%; border: 1px solid rgba(255,255,255,.6); }
|
||||
.pixel-tooltip strong { font-size: 11px; }
|
||||
|
||||
+68
-2
@@ -156,8 +156,18 @@ export default function Home() {
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const cropCanvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const patternStageRef = useRef<HTMLDivElement | null>(null);
|
||||
const cropPreviewRef = useRef<HTMLDivElement | null>(null);
|
||||
const cropDragRef = useRef<{ pointerId: number; x: number; y: number; cropX: number; cropY: number } | null>(null);
|
||||
const patternDragRef = useRef<{
|
||||
pointerId: number;
|
||||
x: number;
|
||||
y: number;
|
||||
scrollLeft: number;
|
||||
scrollTop: number;
|
||||
moved: boolean;
|
||||
} | null>(null);
|
||||
const suppressCanvasClickRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem("bead-pattern-history");
|
||||
@@ -362,7 +372,50 @@ export default function Home() {
|
||||
});
|
||||
};
|
||||
|
||||
const handlePatternPointerDown = (event: React.PointerEvent<HTMLCanvasElement>) => {
|
||||
if (event.button !== 0) return;
|
||||
const stage = patternStageRef.current;
|
||||
if (!stage) return;
|
||||
stage.setPointerCapture(event.pointerId);
|
||||
patternDragRef.current = {
|
||||
pointerId: event.pointerId,
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
scrollLeft: stage.scrollLeft,
|
||||
scrollTop: stage.scrollTop,
|
||||
moved: false,
|
||||
};
|
||||
};
|
||||
|
||||
const handlePatternPointerMove = (event: React.PointerEvent<HTMLCanvasElement>) => {
|
||||
const stage = patternStageRef.current;
|
||||
const drag = patternDragRef.current;
|
||||
if (!stage || !drag || drag.pointerId !== event.pointerId) return;
|
||||
const deltaX = event.clientX - drag.x;
|
||||
const deltaY = event.clientY - drag.y;
|
||||
if (!drag.moved && Math.hypot(deltaX, deltaY) < 5) return;
|
||||
drag.moved = true;
|
||||
stage.classList.add("is-dragging");
|
||||
stage.scrollLeft = drag.scrollLeft - deltaX;
|
||||
stage.scrollTop = drag.scrollTop - deltaY;
|
||||
setHoveredPixel(null);
|
||||
};
|
||||
|
||||
const handlePatternPointerUp = (event: React.PointerEvent<HTMLCanvasElement>) => {
|
||||
const stage = patternStageRef.current;
|
||||
const drag = patternDragRef.current;
|
||||
if (!drag || drag.pointerId !== event.pointerId) return;
|
||||
suppressCanvasClickRef.current = drag.moved;
|
||||
patternDragRef.current = null;
|
||||
stage?.classList.remove("is-dragging");
|
||||
if (stage?.hasPointerCapture(event.pointerId)) stage.releasePointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const handleCanvasClick = (event: React.MouseEvent<HTMLCanvasElement>) => {
|
||||
if (suppressCanvasClickRef.current) {
|
||||
suppressCanvasClickRef.current = false;
|
||||
return;
|
||||
}
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
@@ -499,9 +552,22 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="canvas-stage">
|
||||
<div
|
||||
className="canvas-stage"
|
||||
ref={patternStageRef}
|
||||
>
|
||||
<div className="canvas-wrap">
|
||||
<canvas ref={canvasRef} onClick={handleCanvasClick} onMouseMove={handleCanvasMove} onMouseLeave={() => setHoveredPixel(null)} aria-label="转换后的拼豆像素图,点击格子可选择颜色" />
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
onPointerDown={handlePatternPointerDown}
|
||||
onPointerMove={handlePatternPointerMove}
|
||||
onPointerUp={handlePatternPointerUp}
|
||||
onPointerCancel={handlePatternPointerUp}
|
||||
onClick={handleCanvasClick}
|
||||
onMouseMove={handleCanvasMove}
|
||||
onMouseLeave={() => setHoveredPixel(null)}
|
||||
aria-label="转换后的拼豆像素图,可拖动查看,轻点格子选择颜色"
|
||||
/>
|
||||
{hoveredPixel && <div className="pixel-tooltip" style={{ left: hoveredPixel.left, top: hoveredPixel.top }}>
|
||||
<span className="tooltip-swatch" style={{ backgroundColor: hoveredPixel.color.hex }} />
|
||||
<strong>第 {hoveredPixel.row} 行 · 第 {hoveredPixel.column} 列</strong>
|
||||
|
||||
Reference in New Issue
Block a user