修复资源画本转场克隆的层级与静态不一致
转场层里的克隆只按自身 DOM 顺序叠放,而静态场景中卡片是 25、标题栏是 30,导致动画期间卡片盖住标题栏、结束后又瞬间跳回下方 快照记录元素自身或最近定位祖先的 z-index,克隆按该值设置层级,保持静态层叠关系 补充 resourceBookController 层级回归用例
This commit is contained in:
@@ -5,6 +5,7 @@ type Snapshot = {
|
||||
key: string;
|
||||
rect: { left: number; top: number; width: number; height: number };
|
||||
opacity: number;
|
||||
zIndex: number;
|
||||
content: HTMLElement;
|
||||
};
|
||||
|
||||
@@ -43,6 +44,24 @@ function freezeBlobImageSource(image: HTMLImageElement) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转场层里的克隆只按自己的 DOM 顺序叠放,和静态场景的层级不一致:卡片会盖住
|
||||
* 标题栏,动画结束再瞬间跳回下方。取元素自身或最近定位祖先的 z-index,让克隆
|
||||
* 保持静态时的层叠关系。
|
||||
*/
|
||||
function effectiveZIndex(element: HTMLElement, root: HTMLElement) {
|
||||
for (
|
||||
let node: HTMLElement | null = element;
|
||||
node;
|
||||
node = node.parentElement
|
||||
) {
|
||||
const parsed = Number.parseInt(getComputedStyle(node).zIndex, 10);
|
||||
if (Number.isFinite(parsed)) return parsed;
|
||||
if (node === root) break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Freeze presentation only. Clones cannot expose duplicate controls, replay
|
||||
// media, or depend on CSS selectors belonging to the old ancestor tree.
|
||||
function clonePresentation(
|
||||
@@ -188,6 +207,7 @@ function capture(root: HTMLElement): Map<string, Snapshot> {
|
||||
key,
|
||||
rect,
|
||||
opacity,
|
||||
zIndex: effectiveZIndex(element, root),
|
||||
content: clonePresentation(
|
||||
element.dataset.motionSnapshot
|
||||
? (element.firstElementChild as HTMLElement)
|
||||
@@ -285,6 +305,7 @@ export function createResourceBookTransitionController() {
|
||||
height: `${item.rect.height}px`,
|
||||
transformOrigin: '0 0',
|
||||
pointerEvents: 'none',
|
||||
zIndex: String(item.zIndex),
|
||||
});
|
||||
wrapper.append(item.content);
|
||||
layer.append(wrapper);
|
||||
|
||||
@@ -138,6 +138,46 @@ describe('resource book FLIP controller', () => {
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('keeps the static stacking order inside the transition layer', () => {
|
||||
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-titlebar" style="z-index: 30"><span>标题</span></div>
|
||||
<div class="game-resource-book-scene-card" style="z-index: 25"><div class="game-resource-card" data-resource-card-id="one"><img src="blob:card-preview" alt="" /></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-book-scene-titlebar, .game-resource-card',
|
||||
)) {
|
||||
vi.spyOn(element, 'getBoundingClientRect').mockReturnValue(rect());
|
||||
}
|
||||
|
||||
const controller = createResourceBookTransitionController();
|
||||
const token = controller.begin(root);
|
||||
controller.play(root, token, () => undefined);
|
||||
|
||||
const wrappers = Array.from(
|
||||
root.querySelectorAll<HTMLElement>('[data-motion-snapshot]'),
|
||||
);
|
||||
const titleWrapper = wrappers.find((element) =>
|
||||
element.dataset.motionSnapshot?.startsWith('title:'),
|
||||
);
|
||||
const cardWrapper = wrappers.find((element) =>
|
||||
element.dataset.motionSnapshot?.startsWith('card:'),
|
||||
);
|
||||
expect(Number(titleWrapper?.style.zIndex)).toBe(30);
|
||||
expect(Number(cardWrapper?.style.zIndex)).toBe(25);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('retargets from the current overlay geometry and ignores old completion', async () => {
|
||||
const { root, measure, flights } = setup();
|
||||
const controller = createResourceBookTransitionController();
|
||||
|
||||
Reference in New Issue
Block a user