mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-08-31 02:41:44 +00:00
fix(shader-transitions): pin clones to their measured live box, not composition size
Addresses review feedback on #2254: the previous fix hardcoded every clone to the full composition width/height regardless of the source scene's own sizing, silently overriding any authored explicit width/height. Measuring each scene's getBoundingClientRect() while still live in the document (its inset:0 already resolves correctly against the real ancestor chain there) and reapplying that exact box to the clone fixes the 0x0 collapse without ever clobbering an author's own layout.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clonePinStyleFor,
|
||||
isPageSideCompositingSupported,
|
||||
PAGE_COMPOSITOR_BUILD_CANARY,
|
||||
PAGE_COMPOSITOR_CANVAS_ID,
|
||||
@@ -69,6 +70,26 @@ describe("isPageSideCompositingSupported", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("clonePinStyleFor", () => {
|
||||
it("fixes a 0x0 inset:0 scene root to its live-measured box (the collapse this exists to prevent)", () => {
|
||||
// A scene root sized only by `position:absolute; inset:0` measures as
|
||||
// the full composition frame in the live document (its containing block
|
||||
// there is the real ancestor chain) — collapses to 0x0 only once cloned
|
||||
// into the staging canvas's own layout subtree.
|
||||
const pin = clonePinStyleFor({ left: 0, top: 0, width: 1080, height: 1920 });
|
||||
expect(pin).toEqual({ left: "0px", top: "0px", width: "1080px", height: "1920px" });
|
||||
});
|
||||
|
||||
it("preserves an authored explicit width/height and offset instead of overriding it", () => {
|
||||
// A scene root with its own explicit size/position (e.g. a picture-in-
|
||||
// picture panel) measures as that exact box in the live document —
|
||||
// clonePinStyleFor must reproduce it verbatim, not the full composition
|
||||
// frame, or the clone would silently grow to fill the canvas.
|
||||
const pin = clonePinStyleFor({ left: 120, top: 240, width: 400, height: 300 });
|
||||
expect(pin).toEqual({ left: "120px", top: "240px", width: "400px", height: "300px" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("page-side compositor exported constants", () => {
|
||||
it("exports a stable canary string used by the bundled-CLI smoke", () => {
|
||||
expect(PAGE_COMPOSITOR_BUILD_CANARY).toBe("__hf_page_compositor_v1__");
|
||||
|
||||
@@ -76,6 +76,36 @@ interface ResolvedTransition {
|
||||
export const PAGE_COMPOSITOR_CANVAS_ID = "__hf-page-side-compositor";
|
||||
export const PAGE_COMPOSITOR_BUILD_CANARY = "__hf_page_compositor_v1__";
|
||||
|
||||
export interface ClonePinStyle {
|
||||
left: string;
|
||||
top: string;
|
||||
width: string;
|
||||
height: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Style values to pin a cloned scene root to the box its source measured
|
||||
* WHILE STILL LIVE in the document — never the composition's full pixel size.
|
||||
* A live-document `getBoundingClientRect()` already resolves `inset:0` (and
|
||||
* any authored explicit width/height) correctly against the real ancestor
|
||||
* chain; reapplying that exact box to the clone fixes the 0x0 collapse a
|
||||
* detached `inset:0` clone would otherwise have inside the staging canvas's
|
||||
* layout subtree, without ever overriding an author's own sizing.
|
||||
*/
|
||||
export function clonePinStyleFor(rect: {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}): ClonePinStyle {
|
||||
return {
|
||||
left: `${rect.left}px`,
|
||||
top: `${rect.top}px`,
|
||||
width: `${rect.width}px`,
|
||||
height: `${rect.height}px`,
|
||||
};
|
||||
}
|
||||
|
||||
export function isPageSideCompositingSupported(): boolean {
|
||||
if (typeof window === "undefined" || typeof document === "undefined") return false;
|
||||
if (!isHtmlInCanvasCaptureSupported()) return false;
|
||||
@@ -232,6 +262,17 @@ export function installPageSideCompositor(options: PageCompositorInstallOptions)
|
||||
pWin.__hf_page_composite_pending = false;
|
||||
return false;
|
||||
}
|
||||
// Measure each scene's rendered box WHILE STILL LIVE — a scene root sized
|
||||
// only by `position:absolute; inset:0` resolves to 0x0 once cloned into
|
||||
// the staging canvas's layout subtree (no containing-block dimensions
|
||||
// there), and the transition textures blank out (wild report: explicit
|
||||
// 1080x1920 anchors fixed both transitions). The live document already
|
||||
// resolves inset:0 (and any authored explicit width/height) correctly
|
||||
// against the real ancestor chain, so pinning the clone to THIS measured
|
||||
// box fixes the collapse without ever overriding an author's own sizing.
|
||||
const fromPin = clonePinStyleFor(fromEl.getBoundingClientRect());
|
||||
const toPin = clonePinStyleFor(toEl.getBoundingClientRect());
|
||||
|
||||
while (fromStaging.firstChild) fromStaging.removeChild(fromStaging.firstChild);
|
||||
while (toStaging.firstChild) toStaging.removeChild(toStaging.firstChild);
|
||||
const fromClone = fromEl.cloneNode(true) as HTMLElement;
|
||||
@@ -244,20 +285,17 @@ export function installPageSideCompositor(options: PageCompositorInstallOptions)
|
||||
// paint record" and the shader degrades to a hard cut. The shader blends from
|
||||
// full-opacity textures via u_progress, so force the clones visible. Cf.
|
||||
// forceSceneVisibleInClone (html2canvas path).
|
||||
for (const clone of [fromClone, toClone]) {
|
||||
for (const [clone, pin] of [
|
||||
[fromClone, fromPin],
|
||||
[toClone, toPin],
|
||||
] as const) {
|
||||
clone.style.opacity = "1";
|
||||
clone.style.visibility = "visible";
|
||||
// A scene root sized only by `position:absolute; inset:0` resolves to
|
||||
// 0x0 inside the staging canvas's layout subtree (no containing-block
|
||||
// dimensions there) — the clone then paints nothing and the transition
|
||||
// textures are blank (wild report: explicit 1080x1920 anchors fixed
|
||||
// both transitions). Pin the clone to the composition's pixel size;
|
||||
// scenes that already carry explicit dimensions are unaffected.
|
||||
clone.style.position = "absolute";
|
||||
clone.style.left = "0";
|
||||
clone.style.top = "0";
|
||||
clone.style.width = `${width}px`;
|
||||
clone.style.height = `${height}px`;
|
||||
clone.style.left = pin.left;
|
||||
clone.style.top = pin.top;
|
||||
clone.style.width = pin.width;
|
||||
clone.style.height = pin.height;
|
||||
clone.querySelectorAll<HTMLElement>("[data-start]").forEach((el) => {
|
||||
el.style.opacity = "1";
|
||||
el.style.visibility = "visible";
|
||||
|
||||
Reference in New Issue
Block a user