# 🎬 Deterministic HTML/CSS/JS Animation Rendering ### *Frame-by-Frame Puppeteer β†’ FFmpeg Pipeline with Transparency* This document describes a workflow for rendering **web-based animations** (HTML/CSS/JS, Canvas, WebGL) into **smooth, high-quality videos with alpha transparency**. The goal is to replicate β€œMotion-style” reusable templates (title cards, lower thirds, bumpers) using web technologies. ## πŸ”₯ Why Frame-by-Frame? Running the animation at real-time inside Chrome and screen-recording leads to: * stutter, dropped frames * inconsistent timing * low-quality capture * no alpha channel **Frame-by-frame rendering** creates: * perfectly stable FPS (30, 60, 120, etc.) * deterministic animation timing * super-smooth easing curves * slower-than-real-time rendering for complex effects * transparent PNG β†’ alpha video output This is required for templated, repeatable graphics. --- # 🧱 Overall Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ HTML/CSS/JS page β”‚ ← Your animation template β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Expose a controllable renderFrame(t) β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Puppeteer β”‚ Virtual Browser β”‚ ← headless Chrome β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Step time t = frameIndex / fps β”‚ Screenshot each frame as PNG (with alpha) β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” FFmpeg β”‚ frame_0000.png β”‚ β†’ encode to ProRes 4444 or WebM VP9 alpha β”‚ frame_0001.png β”‚ β”‚ ... β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- # 🧩 Step 1 β€” Build a Time-Controlled Animation Template Create a web page that renders animations based on a **virtual time variable** instead of `performance.now()` or normal `requestAnimationFrame`. ### `renderFrame(t)` Define a global function in JavaScript: ```js window.renderFrame = function(t) { // t = time in seconds // Update CSS variables document.documentElement.style.setProperty("--t", t); // Or animate DOM directly const el = document.querySelector("#title"); el.style.transform = `translateX(${Math.min(0, -200 + t * 400)}px)`; // If using Canvas/WebGL, re-render scene at time t // shader.uniforms.time = t; // renderer.render(scene, camera); }; ``` ### Optional: Live mode So it still animates normally in a browser: ```js if (!window.IS_PUPPETEER) { const start = performance.now(); function tick() { const t = (performance.now() - start) / 1000; window.renderFrame(t); requestAnimationFrame(tick); } tick(); } ``` --- # 🧩 Step 2 β€” Launch Puppeteer and Step Through Frames Use a Node script to render frames deterministically: ### `render.js` ```js import puppeteer from "puppeteer"; import fs from "fs"; import path from "path"; const FPS = 60; const DURATION = 5; // seconds const TOTAL_FRAMES = FPS * DURATION; const WIDTH = 1920; const HEIGHT = 1080; async function main() { const browser = await puppeteer.launch({ headless: "new", defaultViewport: { width: WIDTH, height: HEIGHT }, args: ["--disable-gpu", "--enable-blink-features=CSSColorSchemeUARendering"] }); const page = await browser.newPage(); await page.goto("http://localhost:3000/template?name=Alice&subtitle=Fellow"); await page.addScriptTag({ content: `window.IS_PUPPETEER = true;` }); // Ensure transparent background await page.evaluate(() => { document.body.style.background = "transparent"; }); for (let i = 0; i < TOTAL_FRAMES; i++) { const t = i / FPS; // Advance virtual time await page.evaluate((t) => window.renderFrame(t), t); // Capture frame const buffer = await page.screenshot({ omitBackground: true, // <-- transparency enabled type: "png" }); fs.writeFileSync( path.join("frames", `frame_${String(i).padStart(4, "0")}.png`), buffer ); console.log(`Rendered frame ${i + 1}/${TOTAL_FRAMES}`); } await browser.close(); } main(); ``` --- # 🧩 Step 3 β€” Convert PNG Frames to a Video with Alpha Use FFmpeg to encode transparency. ## Option A: ProRes 4444 (studio/NLE-friendly) Great for DaVinci Resolve, FCP, HyperDeck, ATEM: ```bash ffmpeg -framerate 60 -i frames/frame_%04d.png \ -c:v prores_ks -profile:v 4 -pix_fmt yuva444p10le \ titlecard.mov ``` ## Option B: WebM VP9 with alpha Great for web compositing: ```bash ffmpeg -framerate 60 -i frames/frame_%04d.png \ -c:v libvpx-vp9 -pix_fmt yuva420p \ titlecard.webm ``` --- # 🧩 Step 4 β€” Parameterize Templates (Names, Roles, etc.) Your template page can use query parameters: ``` /template?name=Alice%20Zhang&subtitle=Expos%20Studio%20Fellow ``` Or you can inject data directly: ```js await page.evaluate(({ name, subtitle }) => { document.querySelector("#name").textContent = name; document.querySelector("#subtitle").textContent = subtitle; }, studentData); ``` Then loop over your class roster: ```js for (const student of students) { await renderStudentLowerThird(student); } ``` This is the web-based equivalent of **Final Cut/Motion templates**. --- # 🧩 Step 5 β€” Folder Structure (Recommended) ``` templates/ lower-third/ index.html style.css script.js // contains renderFrame(t) scripts/ render.js // Puppeteer frame renderer batch.js // optional, loop over roster frames/ ...output PNGs... output/ lower-third_Alice.mov lower-third_Benito.mov ``` --- # 🧩 Step 6 β€” Animation Tips for Web β†’ Video Rendering ### Use CSS Variables for Time ```css #title { transform: translateX(calc( var(--t) * 200px )); opacity: calc( min(1, var(--t) * 2) ); } ``` ### Use deterministic easing Provide your easing function in JS: ```js function easeOut(t) { return 1 - Math.pow(1 - t, 3); } ``` ### Avoid built-in `transition` or `animation` timing They're real-time and nondeterministic. Drive everything with **virtual time** instead. ### Canvas/WebGL also works Just re-render your scene at each `t`. --- # 🧩 Optional: Generate 30 Title Cards Automatically If you have a CSV: ```csv name,subtitle Alice Zhang,Expos Fellow Benito Cruz,Vibe Coding Chloe Kim,Harvard '27 ``` Use: ```bash node batch.js students.csv --template lower-third ``` The script: * loads each student * opens the template with params * renders frames for 5 seconds * encodes to transparent video --- # πŸŽ‰ This Pipeline Gives You: ### βœ” Perfectly smooth Motion-quality graphics ### βœ” Deterministic timing ### βœ” Transparency (alpha) ### βœ” Reusable templates with β€œslots” ### βœ” HTML/CSS/JS as your graphics engine ### βœ” FFmpeg as your compositor ### βœ” Batch export of dozens of variants ### βœ” Easy integration with Resolve/ATEM/HyperDeck or web apps It’s the cleanest way to produce **next-generation academic/teaching graphics** without touching native GPU frameworks. --- If you want, I can also produce: * A working **Next.js page template** with a sample animated lower third * A full **CLI tool** for your studio that reads Canvas/Airtable roster β†’ exports videos * A **Makefile** or `npm run render` pipeline * A **Dockerized** version so LLUFs can use it anywhere Just say the word.