Was macht Produkt-Demo-Video?
Verwandle statische Produkt-Screenshots oder Designs in eine polierte, animierte Demo – kein Bildschirmrekorder, kein ruckelnder echter Cursor, keine UI-Versionsabweichung. Rahme jeden Bildschirm ein, gleite einen synthetischen Cursor auf das exakte Pixel, lasse den Klick wellenförmig aufleuchten, zoome dann auf den relevanten Teil und beschrifte das Feature. Da die Eingabe statisch und die Bewegung Code ist, ist jeder Wert deterministisch und wird beim Ändern des Screenshots identisch neu gerendert.
Wann verwenden
- App-/SaaS-Demo, Feature-Ankündigung, Onboarding-Walkthrough (15–90s).
- Zeigen eines Klickpfads durch eine UI, von der du nur Screenshots/Designs hast.
- Hervorheben eines fokussierten Bereichs eines dichten Dashboards (Zoom-zu-Region + Callout).
Warum Screenshots Bildschirmaufnahmen schlagen
| Bildschirmaufnahme | Screenshot-getrieben (dieses Skill) |
|---|---|
| Echter Cursor driftet, überschießt, ruckelt | Cursor folgt einem abgerundeten Pfad zu einem exakten Ziel |
| Gesamte Aufnahme neu aufzeichnen, um einen Schritt zu korrigieren | Ein PNG austauschen, denselben Code neu rendern |
| UI ändert sich während der Aufnahme, Maus sucht | Jeder Bildschirm ist ein sauberes, endgültiges Asset |
| Schwer zu zoomen ohne Pixelbildung | Screenshots in 2× (DPR 2) exportieren → scharfe Zooms |
Erfasse Eingaben mit der doppelten Anzeigegröße (Retina / deviceScaleFactor: 2), damit ein 2×-Zoom scharf bleibt. Siehe references/screenshot-demo.md für ein Playwright-Erfassungsskript.
Die Demo-Schleife
Jede Demo besteht aus demselben Takt, wiederholt pro Feature. Halte lang genug zum Lesen, bewege dich mit Absicht, platziere den Klick, enthülle das Ergebnis.
| Takt | Budget | Aufgabe |
|---|---|---|
| Auf Bildschirm einpendeln | 0.5–1s | Dem Betrachter Zeit geben, sich zu orientieren, bevor sich etwas bewegt |
| Cursor reist zum Ziel | 0.4–0.8s | Abgerundete Bewegung (ease-in-out), nicht linear |
| Klick-Ripple + Zustandsänderung | 0.2–0.3s | Taktiles Feedback; Bildschirm reagiert im selben Frame |
| Zoom in den Fokusbereich | 1–2s hinein, 2–4s halten | 2× ist der Sweet Spot – genug Detail, behält Kontext |
| Feature beschriften | mit dem Zoom halten | Ein Nutzen-Satz, z.B. „Mit einem Klick exportieren“ |
Bewege die Kamera oder den Cursor, selten beides gleichzeitig – zwei gleichzeitige Bewegungen teilen die Aufmerksamkeit.
Screenshot einrahmen
Ein roher Screenshot wirkt wie ein Bug-Report; ein gerahmter wie ein Produkt. Ummantle jeden Bildschirm in einem Browser- oder Geräte-Chrome auf einem brandfarbenen Hintergrund.
// Browser-Frame – Screenshot einfügen, ein glaubwürdiges Fenster erhalten
const BrowserFrame = ({ url, children }) => (
<div style={{ borderRadius: 12, overflow: "hidden", background: "#1e1e22",
boxShadow: "0 40px 80px rgba(0,0,0,.35)" }}>
<div style={{ height: 40, display: "flex", alignItems: "center", gap: 8, padding: "0 14px" }}>
{["#ff5f57", "#febc2e", "#28c840"].map((c) => (
<span key={c} style={{ width: 12, height: 12, borderRadius: "50%", background: c }} />
))}
<div style={{ marginLeft: 12, flex: 1, height: 24, borderRadius: 6, background: "#2b2b30",
color: "#9aa", fontSize: 13, display: "flex", alignItems: "center", padding: "0 10px" }}>
{url}
</div>
</div>
<div style={{ background: "#fff" }}>{children}</div>
</div>
);
Setze eine echte URL in die Adressleiste (Authentizität), füge einen weichen Schatten und einen Farbverlaufshintergrund hinzu. Für Mobilgeräte wechsle zu einem Telefonrahmen mit Notch und abgerundeten Ecken. Behalte den Rahmenstil in einem Theme-Objekt, damit jeder Bildschirm übereinstimmt.
Animierter Cursor + Klick-Ripple (die Kernbewegung)
Treibe die Position vom aktuellen Frame, niemals von einem CSS-Übergang oder einer Wanduhr – ein Video-Renderer benötigt jeden Frame als reine Funktion der Frame-Nummer, sonst desynchronisiert der Cursor und flackert.
import { useCurrentFrame, interpolate, spring, useVideoConfig } from "remotion";
// Abgerundete Reise von `from` nach `to` zwischen startFrame und startFrame+dur
const useCursor = (from, to, startFrame, dur) => {
const frame = useCurrentFrame();
const t = interpolate(frame, [startFrame, startFrame + dur], [0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: (x) => x * x * (3 - 2 * x) }); // smoothstep
return { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };
};
export const Cursor = ({ from, to, startFrame, dur, clickAt }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const { x, y } = useCursor(from, to, startFrame, dur);
// Ripple expandiert einmal, zum Klick-Frame
const since = frame - clickAt;
const showRipple = since >= 0 && since < 18;
const r = spring({ frame: since, fps, config: { damping: 18 }, durationInFrames: 18 });
return (
<>
{showRipple && (
<div style={{ position: "absolute", left: x, top: y, transform: "translate(-50%,-50%)",
width: 12 + r * 60, height: 12 + r * 60, borderRadius: "50%",
border: "2px solid rgba(80,140,255,.9)", opacity: 1 - r }} />
)}
<svg width="28" height="28" viewBox="0 0 28 28"
style={{ position: "absolute", left: x, top: y, filter: "drop-shadow(0 2px 3px rgba(0,0,0,.4))" }}>
<path d="M3 2 L3 21 L9 15 L13 24 L16 23 L12 14 L20 14 Z" fill="#fff" stroke="#222" strokeWidth="1.4" />
</svg>
</>
);
};
Der Cursor verwendet smoothstep, damit er aus dem Stillstand beschleunigt und in das Ziel hinein abbremst (ein linearer Cursor wirkt roboterhaft). Löse das Ripple und die Zustandsänderung des Bildschirms im selben clickAt-Frame aus, damit Ursache und Wirkung übereinstimmen. Siehe references/screenshot-demo.md für die vollständige Komposition, die mehrere Klicks über mehrere Bildschirme hinweg verdrahtet.
In eine Region zoomen (Ken Burns für UI)
Um in ein fokussiertes Element zu zoomen, skaliere den Bildschirm und setze transform-origin auf die Mitte dieses Elements, damit der Zoom darauf zusteuert und nicht auf die Bildschirmmitte.
const ZoomToRegion = ({ target, scale = 2, startFrame, dur, children }) => {
const frame = useCurrentFrame();
const s = interpolate(frame, [startFrame, startFrame + dur], [1, scale],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: (x) => 1 - Math.pow(1 - x, 3) }); // easeOutCubic
return (
<div style={{ transform: `scale(${s})`, transformOrigin: `${target.x}px ${target.y}px`,
willChange: "transform" }}>
{children}
</div>
);
};
Begrenze den Zoom auf etwa 2× (über 3× verliert den Kontext und desorientiert). Halte den gezoomten Zustand 2–4s – lange genug, um das Detail zu lesen –, dann verlasse ihn sanft, bevor du zum nächsten Bildschirm übergehst. Kombiniere den Zoom mit einem Callout, niemals roh.
Callout-Annotationen & Feature-Beschriftungen
Kommentiere den Wendepunkt, dann lass ihn verschwinden. Ein Callout ist ein Label + ein Verbinder (Pfeil, Kreis oder Unterstreichung), der auf das fokussierte Element zeigt.
- Heb das Element mit einem weichen Ring oder abgedunkelten Umfeld hervor (
box-shadow: 0 0 0 9999px rgba(0,0,0,.45)schneidet ein Spotlight-Loch). - Beschrifte einen Nutzen pro Takt – kurze Nominalphrase („Echtzeit-Analysen“), kein Satz. Tritt beim Zoom ein, verlässt vor dem nächsten Schritt.
- Halte Beschriftungen im unteren Drittel und innerhalb des titelsicheren Bereichs, damit sie Zuschnitte auf 9:16 überstehen.
Siehe references/annotation-and-transitions.md für Spotlight-Masken, Pfeilverbinder und eine getippte Beschriftungsspur.
Bildschirm-zu-Bildschirm-Übergänge
Zwischen Schritten bewege Bildschirme mit einer konsistenten Sprache. Wähle eine und bleibe dabei:
| Übergang | Gefühl | Verwendung |
|---|---|---|
| Push / Slide | „nächster Schritt“-Vorwärtsbewegung | Lineare Walkthroughs |
| Cross-Zoom | Schwung, app-ähnlich | In eine Detailansicht springen |
| Crossfade | ruhig, neutral | Kontextwechsel (Einstellungen → Dashboard) |
| Match-Cut auf einem gemeinsamen Element | nahtlos | Dieselbe Karte bleibt über Bildschirme hinweg erhalten |
Halte den ausgehenden Bildschirm, bis sein Klick aufgelöst ist, dann übergang – niemals mitten in der Bewegung schneiden. Siehe references/annotation-and-transitions.md.
Ausgabe-Checkliste
- Eingaben in 2× (DPR 2) erfasst; Zooms bleiben bei 2× scharf.
- Jeder Cursor-/Zoom-Wert ist eine reine Funktion von
useCurrentFrame()– keine CSS-Übergänge für animierten Zustand. - Cursor bewegt sich auf einem abgerundeten Pfad (smoothstep); Ripple und Zustandsänderung werden im selben Klick-Frame ausgelöst.
- Jeder Bildschirm gerahmt (Browser/Gerät) mit einer echten URL und konsistentem Theme.
- Eine Bewegung gleichzeitig (Kamera oder Cursor); Zoom ≤ 2×, 2–4s gehalten.
- Ein Nutzen-Label pro Takt, im titelsicheren unteren Drittel; Übergänge verwenden eine konsistente Sprache.
Liefern & verifizieren (gerenderte Standbilder → MP4)
Verpacktes Hilfsprogramm (
scripts/): tile deine Standbilder mitscripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png, dann überprüfe die Kodierung mitscripts/probe-mp4.sh out.mp4 [WxH] [fps]. Siehescripts/README.md.
Dies ist eine schwere Stufe: Das Lieferobjekt ist ein MP4, keine Live-Seite. Die Demo nimmt Benutzer-Assets – Screenshots/Designs, optionales Logo – entgegen, daher besteht die Verifizierung hauptsächlich darin, ob das richtige Bild tatsächlich geladen und im Rahmen gelandet ist.
Ausgabe-Vertrag:
- Ein Remotion-Projekt mit registrierter Komposition (
<Composition>+ Zodschema+defaultProps); jeder Cursor-/Zoom-/Übergangswert eine reine Funktion vonuseCurrentFrame()(keine CSS-Übergänge, Timer,Date.now()). - Screenshots geladen über
staticFile(), abgesichert mitdelayRender/continueRender, damit das 2×-PNG (und jede Web-Schriftart) vorhanden ist, bevor der Frame gerendert wird – sonst erscheint der Bildschirm leer in der Mitte der Demo. - Lieferobjekt = das gerenderte
out/*.mp4plus das Projekt (erneut rendern, wenn sich der Screenshot ändert).
Verifizierungsschleife – Standbilder rendern → inspizieren → kodieren. Erst günstige PNGs, dann Video, sobald sie korrekt sind.
# Frame-genaue Standbilder MIT DEN PROPERTIES, DIE DU AUSLIEFERN WIRST (echte Screenshot-Pfade), nicht defaultProps
npx remotion still ProductDemo out/f-settle.png --frame=15 --props=demo.json # Bildschirm gerahmt, vor Bewegung
npx remotion still ProductDemo out/f-click.png --frame=90 --props=demo.json # Cursor auf Ziel + Ripple
npx remotion still ProductDemo out/f-zoom.png --frame=150 --props=demo.json # Gezoomt in Fokusbereich + Beschriftung
# End-Frame = durationInFrames - 1 (npx remotion compositions liest es)
Überprüfe jedes PNG auf Wiedergabetreue (korrekter Screenshot geladen; Cursor am exakten Pixel; Klick-Ripple und Zustandsänderung im selben Frame; Beschriftungstext korrekt) UND Artefakte (Bild leer/nicht geladen, Bildschirm außerhalb der Leinwand, Zoom verpixelt oder über den Rahmen hinausgehend, Beschriftung außerhalb des titelsicheren unteren Drittels, falsches Seitenverhältnis/Letterboxing).
# Nur nachdem die Standbilder sauber sind:
npx remotion render ProductDemo out/demo.mp4 --props=demo.json
npx remotion render ProductDemo out/demo.gif --props=demo.json --codec=gif # README-Erster-Bildschirm-Nachweis
Batch (eine Vorlage, viele Produkte): Wenn die Demo pro Produkt neu gestaltet wird, verifiziere EIN repräsentatives Produkt über Standbilder, bevor du den Katalog im Batch renderst – fange einen leeren Bildschirm- oder Off-Canvas-Fehler einmal, nicht N-mal.
Bevor du fertig bist:
- Standbilder rendern sauber beim Einpendeln / Klick / Ende – keine Fehler, Screenshots tatsächlich geladen (nicht leer).
- Cursor landet auf dem exakten Ziel; Ripple + Zustandsänderung im selben Frame; Beschriftungstext korrekt und im titelsicheren unteren Drittel.
- Alle Bewegungen Frame-getrieben – keine CSS-Übergänge / Timer /
Date.now()/Math.random(). - Die ausgelieferten Properties (echte Screenshot-Pfade) rendern korrekt, nicht nur
defaultProps. - Volles MP4 kodiert und abspielbar; (optional) GIF für die README gerendert.
Referenzdateien
references/screenshot-demo.md– eine vollständige ausführbare Remotion-Demo: Erfassen von Screenshots bei 2× mit Playwright, die Browser-/Telefonrahmen-Komponenten, eine mehrschrittige Komposition, die einen Cursor über mehrere Bildschirme mit Klick-Ripples führt, und ein Render-Befehl.references/annotation-and-transitions.md– Spotlight-Masken, Pfeil-/Kreis-Callout-Verbinder, eine getippte Feature-Beschriftungsspur und die vier Bildschirm-zu-Bildschirm-Übergänge mit Frame-getriebenen Implementierungen und Multi-Seitenverhältnis (16:9 / 9:16) sicherere Bereichshinweise.