Motion por código con agente|INEMA.CLUB-PRO

Clase 6 · 15 min

GSAP, D3, Three.js o Lottie: qué biblioteca para cada pieza

Cada tipo de movimiento tiene una biblioteca que lo resuelve en pocas líneas y otra que pelea contigo. La coreografía de texto es GSAP; el gráfico que nace de datos es D3 dibujando y GSAP animando; la escena 3D es Three.js; la animación lista de After Effects es Lottie. En esta lección eliges con la pregunta correcta y renderizas un gráfico y una escena 3D.

Empezar la clase
Cuatro pantallas pequeñas lado a lado en una mesa de edición, con un gráfico de barras, una forma 3D, un ícono de líneas y un título.

En esta clase vas a

  1. 1La pregunta que elige la biblioteca: ¿qué tipo de movimiento es?
  2. 2D3 calcula la forma una vez; GSAP anima en el tiempo
  3. 3Three.js en HyperFrames: hf-seek y data-duration obligatorio
  4. 4El error que el lint detecta cuando falta la duración

Objetivo: elegir la biblioteca correcta para cada pieza y renderizar un gráfico de datos con D3 y GSAP.

01¿Qué tipo de movimiento es?

Haz clic en cada salida. Varias pueden convivir en la misma composición.

Haz clic en cada bloque del diagrama para ver qué hace.

Mapa según los adaptadores documentados en las skills de HyperFrames (GSAP, Lottie, Three.js, Anime.js, CSS, WAAPI, TypeGPU) al 19/10/2026.

02Datos con D3, movimiento con GSAP

Cuatro barras con datos de ejemplo. D3 calcula la escala y la posición; la timeline hace que las barras crezcan en secuencia.

Archivo · kit/exemplos/a6-dados/index.html
<!doctype html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      html, body { width: 1920px; height: 1080px; overflow: hidden; background: #0c0c10; }
      #root { width: 100%; height: 100%; position: relative; font-family: ui-sans-serif, system-ui, sans-serif; }
      #titulo { position: absolute; left: 200px; top: 120px; color: #f4f4f5; font-size: 72px; font-weight: 700; line-height: 1.1; }
      #nota { position: absolute; left: 200px; top: 220px; color: #a1a1aa; font-size: 36px; }
      #grafico { position: absolute; left: 200px; top: 320px; }
      .rotulo { fill: #a1a1aa; font-size: 34px; }
      .valor { fill: #f4f4f5; font-size: 34px; font-weight: 700; }
    </style>
  </head>
  <body>
    <div id="root" data-composition-id="main" data-start="0" data-duration="6" data-width="1920" data-height="1080">
      <h1 id="titulo">Planos por cena</h1>
      <p id="nota">dados de exemplo, para o ensaio</p>
      <svg id="grafico" width="1520" height="600"></svg>
    </div>
    <script>
      // D3 calcula la forma (escalas, posiciones) una vez; GSAP anima en el tiempo.
      const dados = [
        { cena: "Abertura", planos: 4 },
        { cena: "Conflito", planos: 9 },
        { cena: "Virada", planos: 6 },
        { cena: "Fecho", planos: 3 },
      ];
      const svg = d3.select("#grafico");
      const x = d3.scaleLinear().domain([0, 10]).range([0, 1100]);
      const y = d3.scaleBand().domain(dados.map((d) => d.cena)).range([0, 560]).padding(0.3);
      const linha = svg.selectAll("g").data(dados).join("g").attr("transform", (d) => `translate(0,${y(d.cena)})`);
      linha.append("text").attr("class", "rotulo").attr("x", 0).attr("y", y.bandwidth() / 2 + 12).text((d) => d.cena);
      linha.append("rect").attr("class", "barra").attr("x", 260).attr("height", y.bandwidth()).attr("width", (d) => x(d.planos))
        .attr("fill", (d, i) => (i === 1 ? "#e9b949" : "#3f3f47"));
      linha.append("text").attr("class", "valor").attr("x", (d) => 260 + x(d.planos) + 20).attr("y", y.bandwidth() / 2 + 12).text((d) => d.planos);

      const tl = gsap.timeline({ paused: true });
      tl.fromTo("#titulo", { y: 30, opacity: 0 }, { y: 0, opacity: 1, duration: 0.6, ease: "power3.out" }, 0.2);
      tl.fromTo("#nota", { opacity: 0 }, { opacity: 1, duration: 0.4 }, 0.6);
      tl.fromTo(".rotulo", { opacity: 0 }, { opacity: 1, duration: 0.4, stagger: 0.1 }, 0.8);
      tl.fromTo(".barra", { scaleX: 0, transformOrigin: "0% 50%" }, { scaleX: 1, duration: 0.9, ease: "power2.out", stagger: 0.25 }, 1.2);
      tl.fromTo(".valor", { opacity: 0 }, { opacity: 1, duration: 0.3, stagger: 0.25 }, 1.9);
      window.__timelines["main"] = tl;
    </script>
  </body>
</html>
Terminal · check, render y verificar
K=~/projetos/formacao-cinema-ia-motion-v72/kit
export HYPERFRAMES_NO_TELEMETRY=1 NO_COLOR=1
E=${TMPDIR:-/tmp}/ensaio-motion/a6; rm -rf "$E"; mkdir -p "$E"
cp -r "$K/exemplos/a6-dados" "$E/dados"; cd "$E/dados"
npx -y hyperframes@0.8.176 check 2>&1 | grep -E "Check passed|✗"
npx -y hyperframes@0.8.176 render --quality draft --workers 2 --quiet --output dados.mp4 > render.log 2>&1
echo "render: $?"
python3 "$K/conferir.py" dados.mp4 6 1920x1080; echo "saída: $?"
Ejemplo del repositorio
◇  Check passed
render: 0
OK: 6,0 s · 1920×1080 · 30 fps · sem áudio
saída: 0

Las dos bibliotecas vienen del CDN con versión fija (gsap@3.14.2, d3@7.9.0): el render las busca al abrir la página, antes del primer fotograma.

03Three.js atado al tiempo del motor

Una forma girando. La rotación viene del tiempo del evento hf-seek, nunca del reloj. El ensayo quita la duración de la raíz para ver el error y después renderiza la versión correcta.

Archivo · kit/exemplos/a6-three/index.html
<!doctype html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      html, body { width: 1920px; height: 1080px; overflow: hidden; background: #0c0c10; }
      #root { width: 100%; height: 100%; position: relative; }
      #cena3d { position: absolute; inset: 0; width: 1920px; height: 1080px; }
    </style>
  </head>
  <body>
    <div id="root" data-composition-id="main" data-no-timeline data-start="0" data-duration="4" data-width="1920" data-height="1080">
      <canvas id="cena3d"></canvas>
    </div>
    <script type="module">
      // Three.js en HyperFrames: el fotograma viene del tiempo que manda HyperFrames (hf-seek), nunca del reloj.
      import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.181.2/+esm";
      const canvas = document.getElementById("cena3d");
      const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
      renderer.setSize(1920, 1080, false);
      renderer.setPixelRatio(1);
      const scene = new THREE.Scene();
      scene.background = new THREE.Color(0x0c0c10);
      const camera = new THREE.PerspectiveCamera(35, 1920 / 1080, 0.1, 100);
      camera.position.set(0, 0, 6);
      const forma = new THREE.Mesh(
        new THREE.IcosahedronGeometry(1.4, 1),
        new THREE.MeshStandardMaterial({ color: 0xd4d4d8, roughness: 0.4, flatShading: true })
      );
      scene.add(forma);
      scene.add(new THREE.AmbientLight(0xffffff, 0.4));
      const luz = new THREE.DirectionalLight(0xffffff, 2);
      luz.position.set(3, 4, 5);
      scene.add(luz);
      function desenha(t) {
        forma.rotation.y = t * 0.8;
        forma.rotation.x = t * 0.3;
        renderer.render(scene, camera);
      }
      window.addEventListener("hf-seek", (e) => desenha(e.detail.time));
      desenha(window.__hfThreeTime || 0);
    </script>
  </body>
</html>
Terminal · lint sin y con duración, render y verificar
K=~/projetos/formacao-cinema-ia-motion-v72/kit
export HYPERFRAMES_NO_TELEMETRY=1 NO_COLOR=1
E=${TMPDIR:-/tmp}/ensaio-motion/a6; mkdir -p "$E"
rm -rf "$E/three"; cp -r "$K/exemplos/a6-three" "$E/three"; cd "$E/three"
sed -i 's/ data-duration="4"//' index.html   # el error común: raíz sin duración
echo "## sem data-duration"; npx -y hyperframes@0.8.176 lint 2>&1 | grep -oE "root_composition_missing_duration_source|[0-9]+ errors?.*"
cp "$K/exemplos/a6-three/index.html" index.html
echo "## com data-duration"; npx -y hyperframes@0.8.176 lint 2>&1 | grep -oE "root_composition_missing_duration_source|[0-9]+ errors?.*"
npx -y hyperframes@0.8.176 render --quality draft --workers 1 --quiet --output three.mp4 > render.log 2>&1
python3 "$K/conferir.py" three.mp4 4 1920x1080; echo "saída: $?"
Ejemplo del repositorio
## sem data-duration
root_composition_missing_duration_source
1 error(s), 0 warning(s)
## com data-duration
0 errors, 0 warnings
OK: 4,0 s · 1920×1080 · 30 fps · sem áudio
saída: 0

Sin GSAP, la raíz lleva data-no-timeline: en la 0.8.176 el lint pide el registro en window.__timelines y acepta ese atributo como "no tengo timeline" (hallazgo en el código del lint el 19/10/2026). La documentación del skill menciona el atributo para composición estática; aquí la escena se mueve por el hf-seek, no por una timeline.

04Práctica

Dos ensayos y la elección para tus piezas.

E1 · Gráfico animado

10 min

Ejecuta el bloque del gráfico.

Listo cuando: ◇ Check passed, render: 0 y OK: 6,0 s · 1920×1080 · 30 fps · sem áudio con saída: 0.

E2 · 3D sin y con duración

10 min

Ejecuta el bloque de Three.js.

Listo cuando: Sin duración: root_composition_missing_duration_source y 1 error(s); con duración: 0 errors, 0 warnings y OK: 4,0 s · 1920×1080 · 30 fps · sem áudio.

E3 · Tus datos

15 min

En la copia /tmp/ensaio-motion/a6/dados, cambia la lista dados por cuatro números tuyos (y el título), ejecuta check y render de nuevo.

Listo cuando: python3 ~/projetos/formacao-cinema-ia-motion-v72/kit/conferir.py dados.mp4 6 1920x1080; echo "saída: $?" → OK: 6,0 s al comienzo de la línea y saída: 0.

05Prueba de la clase

La explicación aparece después de tu respuesta.

Pregunta 1 de 3

Un gráfico de ventas por mes, con barras que crecen una tras otra. ¿Qué combinación?

06En tres frases

PreguntaCoreografía, datos, 3D, animación lista o repetición: el tipo elige la biblioteca.
CombinaciónD3 calcula una vez, GSAP anima; Three.js dibuja por el hf-seek.
DuraciónSin timeline de GSAP, la raíz necesita data-duration.

Para ir más allá

Clase anteriorPróxima clase