feat(compiler): capture and replay Lottie animations
lottie-web renders vector animations from a JSON document at runtime. As third-party JS it falls outside the declarative CSS path and the WAAPI / reveal / marquee probes, so clones shipped the empty container and the animation was gone. This adds a Lottie subset to Stage 5 motion: capture the deterministic part — the source JSON (materialized local file, or inline animationData) plus playback config — and emit a fixed DittoLottie client that re-mounts lottie-web on the cid'd container. Capture (compiler/src/capture/lottie.ts): in-page detector covering lottie-web's registry (animationData in memory), <lottie-player>/<dotlottie-player>, Elementor data-settings (source_json.url), and generic markup. Keyed by data-cid-cap. motion.ts merges it into MotionCapture; capture.ts registers source URLs as assets so the existing fallback fetch downloads + materializes them to /assets/cloned/lottie. Generate (compiler/src/generate/lottie.ts): buildLottieSpec resolves cap->cid and source URL->materialized local path (or embeds inline animationData), then emits DittoLottie + wire/import helpers, mirroring DittoMotion. app.ts writes the component, wires import+JSX, and injects lottie-web into the generated package.json. Delivery (compiler/src/cli.ts): stripDeliveryDataCids now treats DittoLottie as a runtime consumer, so its container keeps a semantic data-ditto-id anchor instead of being stripped (without this the animation never finds its mount point). Lottie-free clones stay byte-identical. Verified end-to-end: cloning a page with a <lottie-player> scrapes the JSON, emits a wired DittoLottie, and lottie-web mounts and plays the captured animation (rendered SVG viewBox matches the source JSON). Unit tests cover buildLottieSpec resolution + drop rules. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
co-authored by
claude-flow
parent
7d0e85ddb4
commit
048fd8cdad
@@ -1,4 +1,5 @@
|
||||
import type { Page } from "playwright";
|
||||
import { captureLotties, type LottieSpec } from "./lottie.js";
|
||||
|
||||
/**
|
||||
* Stage 5 motion capture. Runs at the canonical viewport AFTER `tagElements` (so every
|
||||
@@ -55,6 +56,8 @@ export type MotionCapture = {
|
||||
rotators: RotatorSpec[];
|
||||
reveals: RevealSpec[]; // scroll-triggered entrance reveals (start hidden, reveal on view)
|
||||
marquees: MarqueeSpec[]; // rAF-driven continuous tickers (Framer Motion etc.) — not in getAnimations()
|
||||
lotties: LottieSpec[]; // lottie-web JSON animations (third-party JS the CSS/WAAPI paths can't reproduce)
|
||||
lottieInline: Record<string, unknown>; // animationData only available in-memory, keyed by LottieSpec.inlineKey
|
||||
cssAnimated: number; // elements with a computed animation-name (informational)
|
||||
};
|
||||
|
||||
@@ -329,7 +332,7 @@ export async function captureMotion(page: Page, opts?: { observeMs?: number; log
|
||||
|
||||
return { waapi, rotators, reveals, cssAnimated };
|
||||
}, observeMs),
|
||||
new Promise<Omit<MotionCapture, "marquees">>((res) => setTimeout(() => res({ waapi: [], rotators: [], reveals: [], cssAnimated: 0 }), observeMs + 4000)),
|
||||
new Promise<Omit<MotionCapture, "marquees" | "lotties" | "lottieInline">>((res) => setTimeout(() => res({ waapi: [], rotators: [], reveals: [], cssAnimated: 0 }), observeMs + 4000)),
|
||||
]);
|
||||
// Scroll-scrub reveals: scroll-linked panels probeReveals can't see (they start only
|
||||
// partially hidden). Merge into reveals, preferring the probe-confirmed entry when a cap
|
||||
@@ -341,10 +344,12 @@ export async function captureMotion(page: Page, opts?: { observeMs?: number; log
|
||||
// Marquees run as a separate pass (scrolls each track into view to wake the paused
|
||||
// rAF ticker; kept after the rotator MutationObserver window so it can't add false text rotators).
|
||||
const marquees = await detectMarquees(page);
|
||||
log({ event: "motion_captured", waapi: result.waapi.length, rotators: result.rotators.length, reveals: reveals.length, marquees: marquees.length, cssAnimated: result.cssAnimated });
|
||||
return { ...result, reveals, marquees };
|
||||
// Lottie: third-party JSON animations, captured separately (registry + static markup scan).
|
||||
const lottie = await captureLotties(page, { log });
|
||||
log({ event: "motion_captured", waapi: result.waapi.length, rotators: result.rotators.length, reveals: reveals.length, marquees: marquees.length, lotties: lottie.lotties.length, cssAnimated: result.cssAnimated });
|
||||
return { ...result, reveals, marquees, lotties: lottie.lotties, lottieInline: lottie.inline };
|
||||
} catch (e) {
|
||||
log({ event: "motion_error", error: String(e).slice(0, 200) });
|
||||
return { waapi: [], rotators: [], reveals: [], marquees: [], cssAnimated: 0 };
|
||||
return { waapi: [], rotators: [], reveals: [], marquees: [], lotties: [], lottieInline: {}, cssAnimated: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user