Nota del autor: desde la indicación inicial y la fórmula de compensación de profundidad de campo hasta la exportación fotograma a fotograma con Playwright + ffmpeg, aquí está el desglose completo de cómo montar con Claude Code un estudio de animación de paralaje 2.5D que «exporta sin perder fotogramas».
Mucha gente que usa por primera vez un asistente de programación con IA para hacer animaciones se encuentra con lo mismo: en el navegador la vista previa va fluida, pero al exportar el vídeo aparecen tirones, saltos de fotogramas y una duración que no cuadra. El problema suele estar en la arquitectura: desde el principio no se pensó la «exportación» como parte del diseño. Este artículo gira en torno a la idea central de función pura renderAt(t) + captura con seek fotograma a fotograma y explica cómo montar, en una sola sesión de Claude Code, un estudio de animación de paralaje 2.5D y exportar MP4 de forma estable.
Esto no es otra demo de «pídele a la IA que te escriba una animación». Aquí desmenuzamos la derivación de la fórmula de compensación de profundidad de campo, un bug de desplazamiento muy típico y la diferencia de fondo entre la reproducción en tiempo real y la exportación fotograma a fotograma.
Valor principal: al terminar de leerlo tendrás una estructura de indicaciones reutilizable, una cadena de exportación reproducible y una lista de verificación que podrás trasladar a otros proyectos de vídeo programático.

Puntos clave del estudio de animación con parallax 2.5D en Claude Code
La práctica que sirve de referencia viene de un artículo publicado el 2026-11-09. El autor usó Claude Code con Opus 5.5 y, en una sola sesión, creó un estudio en un único archivo HTML, más un script de exportación con Playwright de unas 25 líneas. En el portátil del autor, un vídeo de 6 segundos a 720p (180 fotogramas) se exportó en unos 20 segundos. No indica las especificaciones del equipo, así que ese tiempo solo sirve como orden de magnitud.
| Punto clave | Descripción | Valor |
|---|---|---|
| Grafo de escena (Scene Graph) | Cada capa tiene profundidad z, tamaño, posición y contenido HTML | Da sensación de profundidad sin necesidad de modelos 3D |
| Línea de tiempo (Timeline) | Fotogramas clave de la cámara y de las capas, con una curva de suavizado en cada tramo | Los movimientos de cámara se pueden editar y reutilizar |
| Renderizador determinista | renderAt(t) produce una imagen única para cualquier instante t |
El resultado exportado no depende de lo rápida o lenta que sea la máquina |
| Escalado con compensación de profundidad | El tamaño y el desplazamiento se multiplican por 1 - z/perspective |
En reposo se ve exactamente lo que se escribió; en movimiento el parallax sale solo |
| Pipeline de exportación fotograma a fotograma | Playwright hace seek en i/fps, captura la pantalla y se lo pasa a ffmpeg para codificar |
La salida es idéntica en cualquier máquina y sin pérdida de fotogramas |
El principio del parallax 2.5D
El enfoque 2.5D consiste en colocar recursos 2D planos a distintas profundidades de un espacio 3D y hacer que una cámara virtual los atraviese. Las capas más cercanas a la cámara se mueven rápido y las más lejanas, despacio. El ojo humano interpreta esa diferencia de velocidad como profundidad, y eso es el parallax. En todo el proceso no hay modelado, ni iluminación, ni motor 3D: con perspective y preserve-3d de CSS en el navegador es suficiente.
La diferencia entre un «estudio» y una «animación de un solo uso» está en si tiene completas tres piezas: el grafo de escena, la línea de tiempo y el renderizador determinista. Muchos tutoriales se saltan la tercera, pero es la que decide si el MP4 exportado se verá entrecortado o no. Por eso, al escribir la primera línea de la indicación, conviene dejarla fijada como requisito obligatorio.

Configuración del entorno y del modelo para el estudio 2.5D con Claude Code
El material de referencia recomienda usar Opus 5.5 para la primera construcción. En su resumen de modelos, Anthropic lo presenta como un modelo para «programación con agentes de larga duración y trabajo del conocimiento», con 1M de tokens de contexto y hasta 128K de salida. El precio oficial es de 4 / 20 dólares por millón de tokens de entrada / salida; para el precio real, consulta siempre la página oficial.
Conviene fijar el ID concreto del modelo al arrancar, para evitar que un cambio de alias a mitad del proyecto altere el comportamiento:
claude --model claude-opus-5-5 --effort high
Opus 5.5 usa por defecto un esfuerzo (effort) medio. En la primera construcción, el agente tiene que acertar de una vez con las matemáticas de proyección, el motor de fotogramas clave y el pipeline de exportación, así que lo mejor es subirlo a high. Después, para ajustes pequeños como cambiar valores de fotogramas clave o colores, puedes volver a medium y ahorrar tokens.
| Fase | Effort recomendado | Tareas típicas | Motivo |
|---|---|---|---|
| Primera construcción | high | Matemáticas de proyección, motor de fotogramas clave, scripts de exportación | Hay que alinear varios subsistemas a la vez |
| Ajustes iterativos | medium | Retocar fotogramas clave, cambiar la paleta, añadir capas | Los cambios son pequeños y la lógica ya está estable |
| Interfaz «describir la toma» | low (para empezar) | Devolver fotogramas clave en JSON con un esquema fijo | La tarea es corta y la estructura de salida es fija |
El material de referencia no ofrece datos comparativos sobre qué tal funcionan otros modelos en una construcción equivalente ni cuánto cambia el coste. Si quieres cambiar de modelo, lo más sensato es ejecutar la misma indicación con cada uno y comparar punto por punto con el método de validación por fotogramas estáticos que se explica más adelante.
La estructura del prompt para construir un estudio 2.5D con Claude Code
Un prompt bien concreto rinde más que cinco prompts vagos. La especificación de construcción del material de referencia es un único párrafo largo. Esta es una versión que conserva su sentido original:
Build a 2.5D motion studio as one file, studio.html, with no dependencies.
- Scene: 6 layers (sky, moon, hills, city skyline, a title card "Ship it.",
foreground foliage), each at its own z depth, using CSS perspective and preserve-3d.
- Camera: keyframes for x, y, z (dolly) and yaw (orbit around the card).
- Per-layer tracks: opacity, scale, y. Easings: linear, easeInOutCubic,
easeInOutSine, easeOutBack.
- A timeline bar with keyframe markers, a playhead, click to scrub, space to play or pause.
- renderAt(t) must be pure in t. Expose window.__seek(t) that resolves after
the frame paints, and a ?capture mode that hides the UI.
Then write export.mjs: Playwright opens ?capture, seeks every frame at 30 fps,
screenshots it and pipes PNGs to ffmpeg as H.264.
Este prompt se puede dividir en cuatro capas: escena, cámara, pistas con easings e interfaz interactiva, y por último el contrato de exportación. Lo que de verdad marca la diferencia son las dos últimas líneas. Piden que renderAt(t) sea una función pura de t, que se exponga un __seek que solo devuelva el control «cuando el fotograma ya se ha pintado» y que exista un modo ?capture que oculte la interfaz. Así, la capacidad de exportar pasa a ser una restricción de arquitectura, y el agente diseña el código en torno a ella.
Si vas a llevar esto a tu propio proyecto, puedes usar esta estructura como plantilla: primero deja claro «qué dibujar» y, al final, fija en dos o tres líneas «cómo se extraen los fotogramas de forma determinista».
Análisis del código central 2.5D generado por Claude Code
Todo el estudio ronda las 150 líneas, y lo importante está en estas tres partes.
Muestreo de fotogramas clave y suavizado (easing)
Cada pista es un array de fotogramas clave ordenado por tiempo. sample() primero busca los dos fotogramas clave que rodean a t, calcula el progreso con la función de suavizado del fotograma anterior y luego interpola todos los campos numéricos:
function sample(keys, t) {
if (t <= keys[0].t) return keys[0];
for (let i = 0; i < keys.length - 1; i++) {
const a = keys[i], b = keys[i + 1];
if (t <= b.t) {
const p = (easings[a.ease] || easings.linear)((t - a.t) / (b.t - a.t));
const out = {};
for (const k in b)
if (typeof b[k] === 'number' && k !== 't') out[k] = a[k] + (b[k] - a[k]) * p;
return out;
}
}
return keys[keys.length - 1];
}
Esta función no lee la hora actual, no depende del estado del fotograma anterior y tampoco usa números aleatorios. Para un mismo t siempre obtiene el mismo conjunto de valores, y de ahí viene el determinismo de renderAt(t).
Compensación de profundidad: la fórmula central del paralaje 2.5D
La idea es que la cámara no se mueve y es el mundo entero el que se desplaza en sentido contrario. Además de trasladarse en el eje z, cada capa se multiplica por una escala de compensación:
const PERSPECTIVE = 1200; // debe coincidir con la perspective de #viewport
world.style.transform =
`translate3d(${-cam.x}px, ${-cam.y}px, ${cam.z}px) rotateY(${-cam.yaw}deg)`;
const comp = 1 - L.z / PERSPECTIVE;
el.style.transform = `translate(-50%,-50%) ` +
`translate3d(${x * comp}px, ${y * comp}px, ${L.z}px) scale(${comp * s})`;
Esta es la derivación. Con una distancia de perspectiva CSS p, un punto a profundidad z (un z negativo significa que se aleja del observador) se proyecta en pantalla escalado por un factor p / (p - z). Si tomamos p = 1200 y z = -900, obtenemos 1200 / 2100 ≈ 0.571, es decir, la capa queda reducida a un 57 % aproximadamente de su tamaño original.
Para compensar esa reducción, multiplicamos por su inverso: (p - z) / p = 1 - z/p = 1 + 900/1200 = 1.75. Ese es el valor de comp en el código. Como 0.571 × 1.75 = 1, con la cámara quieta cada capa aparece con el tamaño y la posición que hayas definido.
¿Y de dónde sale el paralaje? Cuando la cámara se mueve, todo el world se desplaza el mismo dx, pero el desplazamiento de cada capa en pantalla sigue multiplicándose por su propio p/(p - z). Las capas lejanas tienen un coeficiente pequeño, así que se mueven poco; las cercanas tienen un coeficiente grande, así que se mueven mucho. La compensación solo corrige la composición en reposo y no afecta a la diferencia de velocidades durante el movimiento, que es justo lo que da el efecto 2.5D.
| z de la capa | Escala de proyección p/(p−z) | Coeficiente de compensación comp | Velocidad relativa al desplazar la cámara |
|---|---|---|---|
| 0 (plano de la tarjeta de título) | 1.000 | 1.000 | Referencia |
| -300 | 0.800 | 1.250 | Más lenta |
| -900 | 0.571 | 1.750 | Claramente más lenta |
| -1800 | 0.400 | 2.500 | Casi como un fondo estático |
(Los valores de la tabla están calculados con p = 1200; las filas de -300 y -1800 son derivaciones de ejemplo.)
Un bug que vale la pena recordar: compensar solo el tamaño, no el desplazamiento
La primera versión del código solo aplicaba scale(comp) a las capas, pero los desplazamientos x/y no se multiplicaban por comp. El resultado: el desplazamiento de las colinas lejanas quedaba comprimido por la perspectiva, la composición se descolocaba por completo y las colinas iban subiendo hasta tapar aproximadamente dos tercios del cielo. La solución es sencilla: multiplicar también x e y por comp, porque la perspectiva escala la posición y el tamaño con el mismo coeficiente.
Lo más interesante es cómo se descubrió el bug. El autor lo detectó al ver un solo fotograma renderizado; leyendo el código no lo había notado. Por eso, antes de fiarte de cualquier movimiento de cámara, pídele al agente que renderice tres imágenes fijas: la del inicio, la del punto medio y la del final.

Exportación determinista fotograma a fotograma de animaciones de paralaje 2.5D a MP4
Diferencias de fondo entre la reproducción en tiempo real y la exportación fotograma a fotograma
La vista previa en tiempo real usa requestAnimationFrame, y cuando la máquina va justa de recursos se pierden fotogramas. En la fase de previsualización es aceptable, pero al exportar no se puede perder ni un fotograma. Si grabas la pantalla, lo que capturas es «lo que casualmente se está mostrando en pantalla en ese instante real», así que si te retrasas un fotograma, el resultado es un fotograma repetido o uno que falta.
| Dimensión | Reproducción en tiempo real (rAF / grabación de pantalla) | Exportación con seek fotograma a fotograma |
|---|---|---|
| Fuente del tiempo | Reloj del sistema | Índice del fotograma i / fps |
| Cuando la máquina está ocupada | Fotogramas perdidos y repetidos | Solo va más lento, la imagen no cambia |
| Consistencia entre máquinas | No es consistente | Todas las máquinas generan la misma salida |
| Número de fotogramas | No se puede controlar | Exactamente duration × fps |
| Casos de uso | Vista previa en edición, arrastrar la línea de tiempo | Vídeo final, generación automática en CI |
Script de exportación mínimo
El tiempo del exportador lo marca el número de fotograma. En cada fotograma se hace primero un seek, se espera a que termine de dibujarse la imagen, se captura y luego se escribe el PNG en la entrada estándar de ffmpeg:
import { chromium } from 'playwright';
import { spawn } from 'node:child_process';
import { pathToFileURL } from 'node:url';
const out = process.argv[2] || 'out.mp4';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
await page.goto(pathToFileURL('studio.html').href + '?capture');
const { duration, fps } = await page.evaluate(() => window.__scene);
const ffmpeg = spawn('ffmpeg', ['-y', '-f', 'image2pipe', '-framerate', String(fps),
'-i', '-', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', out],
{ stdio: ['pipe', 'ignore', 'inherit'] });
const frames = Math.round(duration * fps);
for (let i = 0; i < frames; i++) {
await page.evaluate(t => window.__seek(t), i / fps);
ffmpeg.stdin.write(await page.screenshot({ type: 'png' }));
}
ffmpeg.stdin.end();
await new Promise(r => ffmpeg.on('close', r));
await browser.close();
Hay varios detalles que conviene tener en cuenta. window.__seek(t) solo se resuelve después de dos fotogramas de animación, de modo que la captura siempre recoge el transform nuevo. image2pipe hace que ffmpeg lea la secuencia de PNG desde stdin. yuv420p garantiza que el archivo generado se reproduzca sin problemas en el navegador. Si Playwright todavía no tiene el navegador instalado, ejecuta antes una vez npx playwright install chromium.
Ver la implementación de referencia de __seek y de la verificación de fotogramas fijos (código ilustrativo)
// Dentro de studio.html: seek determinista, que vuelve después de dibujar dos fotogramas
window.__seek = (t) => new Promise(resolve => {
renderAt(t); // Función pura: depende solo de t
requestAnimationFrame(() =>
requestAnimationFrame(resolve)); // Asegura que los estilos se han aplicado y dibujado
});
// stills.mjs: renderiza tres fotogramas fijos (inicio / punto medio / final); primero mira la imagen y luego confía en la cámara
import { chromium } from 'playwright';
import { pathToFileURL } from 'node:url';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
await page.goto(pathToFileURL('studio.html').href + '?capture');
const { duration } = await page.evaluate(() => window.__scene);
for (const [name, t] of [['start', 0], ['mid', duration / 2], ['end', duration - 1e-3]]) {
await page.evaluate(t => window.__seek(t), t);
await page.screenshot({ path: `still-${name}.png` });
}
await browser.close();
Los dos fragmentos anteriores son implementaciones ilustrativas escritas siguiendo la idea de los materiales de referencia; lo que valga de verdad es el código que el agente genere en tu proyecto.
Resultados medidos por el autor: 180 fotogramas, 1280×720, 30 fps, duración de 6,0 segundos; el MP4 resultante pesa unos 420 KB y el proceso completo tarda unos 20 segundos. En la imagen, las torres de la ciudad se desplazan menos que las colinas oscuras del primer plano, y estas acaban saliendo por completo del encuadre: eso es justo el efecto de paralaje.

Añadir una función de «describir plano» al estudio 2.5D
El siguiente paso es dejar que el guionista escriba una frase describiendo el plano y que el modelo devuelva un JSON con los fotogramas clave. En el material de referencia se hace llamando directamente a la API Messages de Anthropic:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2026-10-21" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 4096,
"output_config": { "effort": "low" },
"system": "Return only JSON: {\"camera\": [{\"t\",\"x\",\"y\",\"z\",\"yaw\",\"ease\"}], \"tracks\": {}}. Times in seconds, 0 to 6.",
"messages": [{ "role": "user", "content": "Slow push-in, slight orbit right, title fades up in the first second." }]
}'
Hay dos trampas fáciles de pisar. La primera: effort va dentro de output_config.effort. La segunda: Opus 5.5 usa siempre pensamiento adaptativo, así que si envías thinking: {"type": "disabled"} te devuelve un error 400, y además los tokens de pensamiento cuentan dentro de max_tokens. Si pones un max_tokens demasiado bajo, el JSON se corta a mitad del array y stop_reason será "max_tokens".
Una respuesta truncada, o con texto de más mezclado, hará que sample() falle al renderizar, así que conviene acompañar esta llamada con una prueba de contrato. En el material de referencia se usa Apidog CLI para que la misma sesión de Claude Code cree y ejecute las pruebas, con estas aserciones: código de estado 200, stop_reason distinto de "max_tokens", que el texto devuelto se pueda parsear como JSON y que la t del array camera crezca de 0 a 6. Con otra herramienta de pruebas de API, o con un script de aserciones propio, se consigue lo mismo. Vuelve a ejecutar las pruebas cada vez que cambies la indicación de sistema (system prompt) o el effort, porque ambos modifican lo que devuelve el modelo.
Preguntas frecuentes (FAQ)
P1: ¿Qué diferencia hay entre una animación de paralaje 2.5D y una animación 3D de verdad?
El 2.5D coloca recursos planos en 2D a distintas profundidades y los filma con una cámara en movimiento; la sensación de profundidad nace de la diferencia de velocidad entre capas. No necesita modelos 3D, luces ni un motor 3D: basta con CSS en el navegador. A cambio, la cámara no puede rodear las capas en ángulos grandes para ver qué hay detrás.
P2: ¿Por qué no grabar la pantalla y mejor hacer seek y captura fotograma a fotograma?
La grabación de pantalla sigue el tiempo real, así que cualquier fotograma lento se convierte en un fotograma perdido o repetido. Si haces seek a `i / fps` y luego capturas, el contenido de cada fotograma depende solo de su número de índice, y por eso el resultado es idéntico en cualquier máquina.
P3: ¿Cómo se calcula el coeficiente de compensación 1.75?
Con una distancia de perspectiva de 1200 y una profundidad de -900, la escala proyectada es 1200/2100 ≈ 0.571. La compensación es su inverso: `1 – z/p = 1 + 900/1200 = 1.75`. Al multiplicar ambos valores el resultado es 1, de modo que, en reposo, la capa vuelve a su tamaño y posición de diseño.
P4: ¿Por qué se recomienda usar Opus 5.5 con high effort para la primera construcción?
En la primera construcción hay que escribir, en una sola sesión, las matemáticas de proyección, el motor de fotogramas clave y el pipeline de exportación. Según el material de referencia, con una indicación y una ronda de correcciones se obtuvo un estudio funcional. Para los cambios pequeños posteriores basta con medium o menos. Por ahora no hay comparativas públicas con otros modelos, así que tendrás que probarlo por tu cuenta.
P5: ¿Puedo usar el estudio sin clave API?
Sí. El estudio y la exportación a MP4 se ejecutan en local y no necesitan clave. Solo la función «describir plano» llama a la Messages API y requiere tu propia clave API de Anthropic. Los tokens que se consumen al construirlo con Claude Code se facturan según el esquema de cobro de la cuenta que uses.
P6: ¿Qué limitaciones conocidas tiene este método?
La velocidad de captura de PNG fotograma a fotograma depende de la resolución y de la complejidad de la página; los 20 segundos del material de referencia son solo el resultado de una única ejecución en un portátil. El renderizado de CSS 3D con ángulos de yaw grandes y capas semitransparentes puede variar según la versión del navegador. Los ID de modelo, las cabeceras de versión de la API y el parámetro effort caducan con el tiempo, así que revisa la documentación oficial antes de usarlos.
P7: ¿Cómo traslado el enfoque de «renderizado determinista + verificación con fotogramas estáticos» a otros proyectos de vídeo procedural?
Puedes ir revisando esta lista punto por punto:
– La función de renderizado solo recibe t: no lee el reloj del sistema, no depende del estado del fotograma anterior y no usa números aleatorios sin semilla fija
– Ofrece una interfaz de seek que solo devuelva cuando el dibujado haya terminado
– Ofrece un modo capture que oculte la interfaz y use un tamaño de viewport fijo
– El número de fotogramas exportados debe ser exactamente `round(duration × fps)`
– Tras modificar un plano, renderiza primero tres fotogramas estáticos (inicio, mitad y final) y, cuando todo esté bien, exporta el vídeo completo
– Añade pruebas de contrato al JSON de fotogramas clave generado por el modelo: que se pueda parsear, que el tiempo sea estrictamente creciente y que no esté truncado
Claude Code 2.5D: puntos clave del estudio de animación con paralaje
- La exportación es parte del diseño: en las últimas líneas de la indicación, fija una función pura
renderAt(t)y un contrato deseek. - Fórmula de compensación de profundidad:
comp = 1 − z/perspective; hay que multiplicarla tanto por el tamaño como por los desplazamientos x/y. - De dónde sale el paralaje: la compensación solo corrige la composición estática; cuando hay movimiento, cada capa se muestra con una velocidad distinta según su profundidad.
- Primero mira los fotogramas fijos, luego confía en el movimiento de cámara: tres imágenes (inicio, mitad y final) revelan bugs que no se ven leyendo el código.
- Controla el tiempo por número de fotograma: Playwright hace
seeky captura la pantalla; ffmpeg lo codifica en H.264 medianteimage2pipe. - Ajusta el
effortpor fases: usahighpara la primera construcción,mediumpara los retoques y empieza conlowen las interfaces JSON estructuradas. - Añade pruebas de contrato a la salida del modelo: evitan que un truncado por
max_tokensrompa el análisis de los fotogramas clave.
Resumen
Para montar un estudio de animación 2.5D con paralaje usando Claude Code, hay tres ideas centrales:
- Arquitectura: grafo de escena, línea de tiempo y renderizador determinista. La tercera pieza es la que decide la calidad del vídeo final.
- Matemáticas: el coeficiente de compensación de perspectiva
1 - z/pactúa a la vez sobre el tamaño y el desplazamiento. En reposo la composición es exacta, y en movimiento el paralaje aparece de forma natural. - Pipeline: se hace
seekfotograma a fotograma coni/fps, se captura la pantalla y se pasa a ffmpeg para codificar. Así el resultado no depende del rendimiento de la máquina.
Te recomiendo que primero sigas la estructura de indicaciones de este artículo hasta sacar un clip de prueba de 6 segundos, y que adquieras el hábito de «mirar tres fotogramas fijos antes de tocar la cámara». Después puedes añadir más capas, integrar una interfaz de «descripción de plano», o trasladar esta misma idea determinista a otras soluciones de vídeo programático como Remotion.
📚 Referencias
- How to Build a 2.5D Motion Studio With Opus 5.5: fuente práctica de este artículo
- Enlace:
apidog.com/blog/build-2-5d-motion-studio-opus-5-5 -
Descripción: incluye las indicaciones completas, el código del estudio, el script de exportación y ejemplos de la interfaz «describir plano»
-
Documentación de modelos y effort de Anthropic: posicionamiento del modelo, ventana de contexto, precios y explicación del parámetro effort
- Enlace:
docs.anthropic.com -
Descripción: sirve para verificar el ID del modelo claude-opus-5-5, la cabecera de versión de la API y el uso de output_config
-
Documentación de formatos de ffmpeg: explicación de formatos de entrada como image2pipe
- Enlace:
ffmpeg.org/ffmpeg-formats.html -
Descripción: ayuda a entender cómo se lee una secuencia de PNG desde stdin y los parámetros de codificación H.264
-
Documentación de Playwright: automatización del navegador y API de capturas de pantalla
- Enlace:
playwright.dev - Descripción: cubre page.evaluate, page.screenshot y el comando de instalación del navegador
