1
This commit is contained in:
@@ -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}%`,
|
||||
|
||||
Reference in New Issue
Block a user