修复资源画本转场克隆的遮挡顺序跳变

层级相同的卡片由 DOM 顺序决定遮挡,而转场层原先按来源 key 在前追加克隆,和动画结束后的目标场景顺序不一致,结束瞬间会跳变
改为先按目标场景 DOM 顺序排列,再补只存在于来源里的 key
补充 resourceBookController 用例覆盖来源与目标顺序不同时的克隆顺序
This commit is contained in:
2026-09-09 16:43:54 +08:00
parent 04ab7ee0da
commit 9a3313fe56
2 changed files with 46 additions and 1 deletions
@@ -285,7 +285,12 @@ export function createResourceBookTransitionController() {
return; return;
} }
const target = capture(manager); const target = capture(manager);
const keys = new Set([...source.keys(), ...target.keys()]); // 先按目标场景的 DOM 顺序排,再补上只存在于来源里的 key。层级相同的卡片
// 由 DOM 顺序决定遮挡关系,顺序不一致会让动画结束的瞬间发生跳变。
const keys = [
...target.keys(),
...Array.from(source.keys()).filter((key) => !target.has(key)),
];
if (!target.size) { if (!target.size) {
settle(); settle();
return; return;
@@ -178,6 +178,46 @@ describe('resource book FLIP controller', () => {
controller.dispose(); controller.dispose();
}); });
it('appends clones in the target DOM order so equal z-index cards keep their occlusion', () => {
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);
const wrappers = Array.from(
root.querySelectorAll<HTMLElement>('[data-motion-snapshot]'),
);
expect(wrappers.map((element) => element.dataset.motionSnapshot)).toEqual([
'card:b',
'card:a',
]);
controller.dispose();
});
it('retargets from the current overlay geometry and ignores old completion', async () => { it('retargets from the current overlay geometry and ignores old completion', async () => {
const { root, measure, flights } = setup(); const { root, measure, flights } = setup();
const controller = createResourceBookTransitionController(); const controller = createResourceBookTransitionController();