This commit is contained in:
2026-04-28 02:05:12 +08:00
parent 271db02e4a
commit 1eb090e4a5
39 changed files with 2671 additions and 165 deletions

View File

@@ -65,6 +65,35 @@ function buildLocalCellKey(row: number, col: number) {
return `${row}:${col}`;
}
export function resolveDraggedPieceCellLayer(
pieceId: string | null | undefined,
draggingPieceId: string | null,
isMerged: boolean,
) {
if (!pieceId || isMerged || pieceId !== draggingPieceId) {
return undefined;
}
return 80;
}
export function resolveDraggedPieceLayer(
pieceId: string | null | undefined,
draggingPieceId: string | null,
isMerged: boolean,
) {
if (!pieceId || isMerged || pieceId !== draggingPieceId) {
return undefined;
}
return 81;
}
export function resolveDraggedMergedGroupLayer(
groupId: string,
draggingGroupId: string | null,
) {
return groupId === draggingGroupId ? 90 : undefined;
}
function resolveMergedPieceOutlineClass(
group: PuzzleMergedGroupViewModel,
piece: PuzzleMergedGroupViewModel['pieces'][number],
@@ -186,8 +215,13 @@ export function PuzzleRuntimeShell({
} | null>(null);
const dragVisualFrameRef = useRef<number | null>(null);
const dragOffsetRef = useRef<{ x: number; y: number } | null>(null);
const pieceCellElementRefMap = useRef(new Map<string, HTMLDivElement>());
const pieceElementRefMap = useRef(new Map<string, HTMLDivElement>());
const groupElementRefMap = useRef(new Map<string, HTMLDivElement>());
const [dragRenderTarget, setDragRenderTarget] = useState<{
pieceId: string;
groupId: string | null;
} | null>(null);
const [dismissedClearKey, setDismissedClearKey] = useState<string | null>(
null,
);
@@ -250,8 +284,18 @@ export function PuzzleRuntimeShell({
[pieces],
);
const resolvePieceCellElement = (pieceId: string) => {
const pieceElement = pieceElementRefMap.current.get(pieceId) ?? null;
const pieceCellElement =
(pieceElement?.parentElement as HTMLDivElement | null) ??
pieceCellElementRefMap.current.get(pieceId) ??
null;
return pieceCellElement;
};
const resetDragVisualTarget = () => {
const dragVisualTarget = dragVisualTargetRef.current;
setDragRenderTarget(null);
if (!dragVisualTarget) {
return;
}
@@ -259,6 +303,10 @@ export function PuzzleRuntimeShell({
const pieceElement = pieceElementRefMap.current.get(
dragVisualTarget.pieceId,
);
const pieceCellElement = resolvePieceCellElement(dragVisualTarget.pieceId);
if (pieceCellElement) {
pieceCellElement.style.zIndex = '';
}
if (pieceElement) {
pieceElement.style.transform = '';
pieceElement.style.willChange = '';
@@ -319,6 +367,15 @@ export function PuzzleRuntimeShell({
resetDragVisualTarget();
}
dragVisualTargetRef.current = nextTarget;
setDragRenderTarget((currentTarget) => {
if (
currentTarget?.pieceId === nextTarget.pieceId &&
currentTarget.groupId === nextTarget.groupId
) {
return currentTarget;
}
return nextTarget;
});
const offsetX = dragSession.currentX - dragSession.startX;
const offsetY = dragSession.currentY - dragSession.startY;
@@ -327,11 +384,16 @@ export function PuzzleRuntimeShell({
if (groupId) {
const groupElement = groupElementRefMap.current.get(groupId);
if (groupElement) {
// 合并块拖动时直接提升整个组容器层级,确保完整拼块永远压在单块之上。
groupElement.style.willChange = 'transform';
groupElement.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0) scale(1.02)`;
groupElement.style.zIndex = '80';
groupElement.style.zIndex = '90';
groupElement.style.opacity = '0.95';
}
const pieceCellElement = resolvePieceCellElement(dragSession.pieceId);
if (pieceCellElement) {
pieceCellElement.style.zIndex = '';
}
const pieceElement = pieceElementRefMap.current.get(dragSession.pieceId);
if (pieceElement) {
pieceElement.style.transform = '';
@@ -342,11 +404,16 @@ export function PuzzleRuntimeShell({
return;
}
const pieceCellElement = resolvePieceCellElement(dragSession.pieceId);
if (pieceCellElement) {
// 单块拖动时提升所属格子的堆叠层级,避免被后绘制的拼块或合并块遮住。
pieceCellElement.style.zIndex = '80';
}
const pieceElement = pieceElementRefMap.current.get(dragSession.pieceId);
if (pieceElement) {
pieceElement.style.willChange = 'transform';
pieceElement.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0) scale(1.03)`;
pieceElement.style.zIndex = '70';
pieceElement.style.zIndex = '81';
pieceElement.style.opacity = '0.95';
}
};
@@ -559,7 +626,8 @@ export function PuzzleRuntimeShell({
return;
}
// 拖动中的视觉更新直接写入 DOM transform避免 pointermove 触发整盘 React 重渲染导致跟手延迟
// 首帧拖拽反馈立即落到 DOM确保层级提升不会滞后一帧后续仍保留 raf 兜底连续刷新
flushDragVisual();
scheduleDragVisual();
};
@@ -575,6 +643,8 @@ export function PuzzleRuntimeShell({
currentLevel.status === 'cleared' &&
dismissedClearKey !== clearResultKey &&
isClearResultReady;
const draggingPieceId = dragRenderTarget?.pieceId ?? null;
const draggingGroupId = dragRenderTarget?.groupId ?? null;
return (
<div className="fixed inset-0 z-[100] flex justify-center bg-slate-950 text-white">
@@ -643,7 +713,28 @@ export function PuzzleRuntimeShell({
const isSelected = piece?.pieceId === selectedPieceId;
return (
<div key={`${cell.row}:${cell.col}`} className="relative p-1">
<div
key={`${cell.row}:${cell.col}`}
ref={(node) => {
if (!piece) {
return;
}
if (node) {
pieceCellElementRefMap.current.set(piece.pieceId, node);
return;
}
pieceCellElementRefMap.current.delete(piece.pieceId);
}}
data-piece-cell-id={piece?.pieceId ?? undefined}
className="relative p-1"
style={{
zIndex: resolveDraggedPieceCellLayer(
piece?.pieceId,
draggingPieceId,
isMerged,
),
}}
>
<div
ref={(node) => {
if (!piece) {
@@ -656,7 +747,7 @@ export function PuzzleRuntimeShell({
pieceElementRefMap.current.delete(piece.pieceId);
}}
data-piece-id={piece?.pieceId ?? undefined}
className={`flex h-full min-h-[4.5rem] items-center justify-center rounded-[1rem] border text-sm font-black transition ${
className={`relative flex h-full min-h-[4.5rem] items-center justify-center rounded-[1rem] border text-sm font-black transition ${
occupied
? isSelected
? 'border-amber-200 bg-amber-400/84 text-slate-950 shadow-[0_12px_30px_rgba(251,191,36,0.22)]'
@@ -669,6 +760,13 @@ export function PuzzleRuntimeShell({
? 'transition-colors'
: 'transition-[background-color,border-color,box-shadow,opacity]'
}`}
style={{
zIndex: resolveDraggedPieceLayer(
piece?.pieceId,
draggingPieceId,
isMerged,
),
}}
onPointerDown={(event) => {
if (!piece || isMerged) {
return;
@@ -734,8 +832,13 @@ export function PuzzleRuntimeShell({
}
groupElementRefMap.current.delete(group.groupId);
}}
data-merged-group-id={group.groupId}
className="pointer-events-none absolute z-10 p-1"
style={{
zIndex: resolveDraggedMergedGroupLayer(
group.groupId,
draggingGroupId,
),
left: `${(group.minCol / board.cols) * 100}%`,
top: `${(group.minRow / board.rows) * 100}%`,
width: `${(group.colSpan / board.cols) * 100}%`,