用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tools-only/X-Skills --skill remotion-integration命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | remotion-integration |
| description | Core patterns for integrating GSAP timelines with Remotion's frame-based rendering |
| metadata | {"tags":"gsap, remotion, integration, useGSAPTimeline, determinism, frame-based"} |
GSAP timelines are created paused and seeked to frame / fps on every frame. Remotion controls time; GSAP provides animation logic. This produces deterministic, frame-perfect video output.
Remotion Frame -> Time Conversion -> GSAP Timeline Seek
frame = 0 -> tl.seek(0) -> start state
frame = 15 -> tl.seek(0.5) -> 0.5s state (@ 30fps)
frame = 30 -> tl.seek(1.0) -> 1.0s state
function useGSAPTimeline(
buildTimeline: (tl: gsap.core.Timeline, container: HTMLDivElement) => void
) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const containerRef = useRef<HTMLDivElement>(null);
const tlRef = useRef<gsap.core.Timeline | null>(null);
// Build timeline once (paused)
useEffect(() => {
if (!containerRef.current) return;
const ctx = gsap.context(() => {
const tl = gsap.timeline({ paused: true });
buildTimeline(tl, containerRef.current!);
tlRef.current = tl;
}, containerRef);
return () => { ctx.revert(); tlRef.current = null; };
}, []);
// Seek to current frame
useEffect(() => {
if (tlRef.current) tlRef.current.seek(frame / fps);
}, [frame, fps]);
return containerRef;
}
SplitText measures text dimensions, so fonts must be loaded first. Use delayRender() to block rendering until ready.
function useGSAPWithFonts(
buildTimeline: (tl: gsap.core.Timeline, container: HTMLDivElement) => void
) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const containerRef = useRef<HTMLDivElement>(null);
const tlRef = useRef<gsap.core.Timeline | null>(null);
const [handle] = useState(() => delayRender());
useEffect(() => {
document.fonts.ready.then(() => {
if (!containerRef.current) return;
const ctx = gsap.context(() => {
const tl = gsap.timeline({ paused: true });
buildTimeline(tl, containerRef.current!);
tlRef.current = tl;
}, containerRef);
continueRender(handle);
return () => { ctx.revert(); };
});
}, []);
useEffect(() => {
if (tlRef.) tlRef..(frame / fps);
}, [frame, fps]);
containerRef;
}
tl.play(), never tl.resume()Date.now(), setTimeout, requestAnimationFrame, gsap.tickerMath.random() or gsap.utils.random(); use seeded PRNGonUpdate that accumulates state// Seeded random for deterministic "random" values
function seededRandom(seed: number): number {
const x = Math.sin(seed) * 10000;
return x - Math.floor(x);
}
// Deterministic stagger (instead of from: "random")
tl.from(items, {
y: 100, opacity: 0,
stagger: (index) => seededRandom(index * 7919) * 0.5,
});
// Calculate Composition durationInFrames from GSAP timeline
const tl = gsap.timeline({ paused: true });
// ... build timeline ...
const totalSeconds = tl.totalDuration();
const durationInFrames = Math.ceil(totalSeconds * fps);
useEffect([], []), seek in useEffect([frame])gsap.context() -- scopes animations to container, enables clean revertReact.memo for static containers around animated elements<Freeze> for elements after their animation completesseek() resolves all tweens at positionfilter: blur() is slow without GPUUse GSAP for complex sequences, Remotion interpolate() for simple properties:
const MyScene: React.FC = () => {
const frame = useCurrentFrame();
// Simple fade via Remotion native (no GSAP needed)
const bgOpacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: 'clamp' });
// Complex text sequence via GSAP
const containerRef = useGSAPTimeline((tl, container) => {
tl.from(container.querySelectorAll('.char'), {
opacity: 0, y: 50, rotationX: -90,
stagger: 0.05, duration: 0.8, ease: 'back.out(1.7)',
});
});
return (
<AbsoluteFill style={{ opacity: bgOpacity }}>
<div ref={containerRef}>...</div>
</AbsoluteFill>
);
};
Remotion renders frames in multiple browser tabs in parallel. Each tab:
This works correctly because:
seek() is stateless (same time = same output)Avoid:
useState for animation values (derive from useCurrentFrame())Use Remotion's <Audio> + <Sequence> for frame-synced audio:
<AbsoluteFill>
<Audio src={staticFile('bgm.mp3')} volume={0.5} />
<Sequence from={30} durationInFrames={60}>
<Audio src={staticFile('whoosh.mp3')} volume={0.8} />
</Sequence>
<GSAPAnimatedScene />
</AbsoluteFill>