修复资源画本飞行期间遮挡沿用错误一侧顺序

卡片真正重叠的是堆叠那一侧:进入时重叠在总览摞(来源),返回时重叠在摞(目标)。原先统一按目标顺序排,进入时就用错了顺序,堆叠里下面两张飞到上面,结束才回到下方
play 增加 stacking 参数:进入用来源顺序,返回用目标顺序;不在该侧的卡片压到最底层
补充 resourceBookController 用例覆盖来源顺序
This commit is contained in:
2026-09-09 16:58:44 +08:00
parent a655ee1b1e
commit 0914703032
3 changed files with 66 additions and 10 deletions
@@ -2550,6 +2550,8 @@ export default function ProjectDevelopmentView({
token: resourceBookState.token,
});
},
// 进入时重叠在总览摞上,沿用来源顺序;返回时重叠在摞上,沿用目标顺序。
resourceBookState.phase === 'returning-main' ? 'target' : 'source',
);
}, [resourceBookState.token, resourceBookState.phase, mode]);
@@ -271,7 +271,12 @@ export function createResourceBookTransitionController() {
invalidate,
settle,
isCurrent: (expected: number) => token === expected,
play(manager: HTMLElement, expected: number, done: () => void) {
play(
manager: HTMLElement,
expected: number,
done: () => void,
stacking: 'source' | 'target' = 'target',
) {
if (token !== expected) return;
root = manager;
completion = done;
@@ -292,19 +297,28 @@ export function createResourceBookTransitionController() {
settle();
return;
}
// 两个场景的 z-index 数值本身不同(总览 1–3、子画布 25),只搬数值无法保证
// 顺序一致。按目标场景的最终绘制顺序(z-index 升序,同值再按 DOM 顺序)
// 给克隆分配单调层级,动画期间的遮挡关系就与结束后的静止状态完全一致;
// 只存在于来源里的淡出项统一压到最底层,不会盖住存活卡片。
const ordered = [
...Array.from(target.values()).sort(
(left, right) =>
left.zIndex - right.zIndex || left.order - right.order,
),
// 两个场景的 z-index 数值域不同(总览 1–3、子画布 25),只搬数值保证不了顺序。
// 卡片真正重叠的是"堆叠"那一侧:进入时重叠在总览摞(来源),返回时重叠在摞
// (目标)。按该侧 z-index 升序、同值再按 DOM 顺序给克隆分配单调层级,飞行期间
// 的遮挡就和用户眼前那一摞一致;不在该侧的卡片压到最底层。
const reference = stacking === 'source' ? source : target;
const entries = [
...Array.from(target.values()),
...Array.from(source.values()).filter(
(entry) => !target.has(entry.key),
),
];
const primary = entries.filter((entry) => reference.has(entry.key));
const secondary = entries.filter((entry) => !reference.has(entry.key));
primary.sort((left, right) => {
const leftReference = reference.get(left.key)!;
const rightReference = reference.get(right.key)!;
return (
leftReference.zIndex - rightReference.zIndex ||
leftReference.order - rightReference.order
);
});
const ordered = [...primary, ...secondary];
try {
for (const [stackOrder, item] of ordered.entries()) {
const from = source.get(item.key);
@@ -224,6 +224,46 @@ describe('resource book FLIP controller', () => {
controller.dispose();
});
it('keeps the source stack order while flying out of the overview', () => {
Object.defineProperty(Element.prototype, 'animate', {
configurable: true,
writable: true,
value: () => ({
finished: Promise.resolve({} as Animation),
cancel: () => undefined,
}),
});
const root = document.createElement('div');
root.innerHTML = `<div class="game-resource-book-scene"><div class="game-resource-book-scene-world">
<div class="game-resource-book-scene-card"><div class="game-resource-card" data-resource-card-id="a"></div></div>
<div class="game-resource-book-scene-card"><div class="game-resource-card" data-resource-card-id="b"></div></div>
</div></div><div class="game-resource-book-transition-layer" inert aria-hidden="true"></div>`;
document.body.append(root);
vi.spyOn(root, 'getBoundingClientRect').mockReturnValue(rect(0, 0, 800, 600));
for (const element of root.querySelectorAll<HTMLElement>(
'.game-resource-card',
)) {
vi.spyOn(element, 'getBoundingClientRect').mockReturnValue(rect());
}
const controller = createResourceBookTransitionController();
const token = controller.begin(root);
const cardWrapper = root
.querySelector<HTMLElement>('[data-resource-card-id="a"]')!
.closest<HTMLElement>('.game-resource-book-scene-card')!;
cardWrapper.parentElement!.append(cardWrapper);
controller.play(root, token, () => undefined, 'source');
const wrappers = Array.from(
root.querySelectorAll<HTMLElement>('[data-motion-snapshot]'),
);
expect(wrappers.map((element) => element.dataset.motionSnapshot)).toEqual([
'card:a',
'card:b',
]);
controller.dispose();
});
it('retargets from the current overlay geometry and ignores old completion', async () => {
const { root, measure, flights } = setup();
const controller = createResourceBookTransitionController();