Animación de Pizarra Blanca
El aspecto de VideoScribe / Doodly: una mano dibuja ilustraciones y texto a mano en una pizarra, trazo por trazo, mientras una narración explica. Se construye a partir de rutas SVG animadas que se dibujan a sí mismas más una mano marcadora que sigue la punta del dibujo, luego se secuencian los elementos para que la pizarra se llene en el orden de la narración.
Cuándo usar
- Un explicador "scribe" / doodle donde una mano dibuja arte y texto en una pizarra blanca.
- Animar un boceto o logotipo que se dibuja en vivo, línea por línea.
- Texto escrito a mano que aparece solo, sincronizado con una voz en off.
- Convertir una ilustración estática en un revelado guiado, trazo por trazo.
Mecánica central: dibujo de trazos SVG
Una ruta "se dibuja a sí misma" animando stroke-dashoffset desde su longitud total hasta 0 — el espacio del guión se reduce, exponiendo el trazo desde el inicio hasta el final.
const path = document.querySelector("#stroke");
const len = path.getTotalLength();
path.style.strokeDasharray = len; // un guión tan largo como toda la ruta
path.style.strokeDashoffset = len; // empujado completamente fuera de la vista = invisible
path.getBoundingClientRect(); // forzar reflow antes de la transición
path.style.transition = "stroke-dashoffset 1.2s linear";
path.style.strokeDashoffset = "0"; // dibuja inicio → fin
Usa pathLength="1" en el <path> para normalizar: entonces stroke-dasharray:1; stroke-dashoffset:1 dibuja cualquier ruta independientemente de su longitud real, y un único valor suavizado (0→1) es el progreso del dibujo. linear (o una suavización muy suave) se lee como una mano firme; evita ease-in-out en trazos largos — hace que el marcador se sacuda.
La mano marcadora sigue la punta
La ilusión vive o muere en que la mano se sitúe en el punto exacto que se está dibujando. Muestrea la ruta en el progreso actual del dibujo y coloca la imagen de la mano allí en cada fotograma; la punta del bolígrafo del activo de la mano debe alinearse con el punto de la ruta (desplaza la imagen para que su punta, no su esquina, caiga en el punto).
const hand = document.querySelector("#hand"); // <img>/<g>, punta en su "nib" superior izquierdo
const NIB = { x: 6, y: 4 }; // desplazamiento del nib dentro del arte de la mano
function drawOn(path, hand, dur = 1200) {
const len = path.getTotalLength(), t0 = performance.now();
path.style.strokeDasharray = len;
(function frame(now) {
const k = Math.min(1, (now - t0) / dur); // progreso 0→1
path.style.strokeDashoffset = len * (1 - k); // expone inicio→punta
const p = path.getPointAtLength(len * k); // punta actual en coordenadas SVG
hand.setAttribute("transform", `translate(${p.x - NIB.x} ${p.y - NIB.y})`);
if (k < 1) requestAnimationFrame(frame);
})(t0);
}
getPointAtLength(len * k) es el caballo de batalla: devuelve la coordenada en la ruta en el mismo progreso que impulsa strokeDashoffset, por lo que el nib permanece pegado al trazo que crece. Levanta la mano (desvanece, salta al siguiente inicio) entre trazos separados para que no se deslice por la pizarra en blanco.
Orden de revelado — la pizarra se llena
Secuencia los elementos de la manera en que una persona dibujaría la escena, en orden de narración: cada ilustración y cada bloque de texto escrito a mano se dibuja, se mantiene, luego comienza el siguiente. Escalonar los trazos dentro de un dibujo; mantener una pausa después de que cada elemento aterrice antes de mover la mano. Un dibujo = un tiempo de la voz en off. No dibujes dos cosas a la vez — el ojo (y la mano única) solo puede seguir una punta.
| Paso | Mecanismo |
|---|---|
| Dibujar un trazo | stroke-dashoffset len→0, mano en getPointAtLength(len*k) |
| Dibujo de múltiples trazos | escalonar trazos; la mano salta al inicio de cada nuevo trazo |
| Texto escrito a mano | trazos por letra/palabra dibujados en orden de lectura, la mano sigue |
| Pausa | pausar la mano fuera de la pizarra después de que el elemento se complete (0.4–1.0s) |
| Siguiente elemento | mover/desvanecer la mano al siguiente inicio; comenzar su dibujo |
| Borrar / limpiar | barrer o dibujar inversamente para limpiar la pizarra para la siguiente sección |
Conversión de arte a rutas dibujables
El dibujo necesita rutas abiertas de un solo trazo en orden de dibujo — no formas compuestas rellenas.
- Crea o calca arte lineal como trazos abiertos (Illustrator/Inkscape: contorno, no relleno). Un blob relleno no tiene "trazo" que animar; convierte los rellenos en un trazo de línea central o dibuja una ruta de contorno y rellénala después de que el contorno se complete.
- El orden importa: los elementos
<path>deben aparecer en el SVG en el orden en que una mano los dibujaría — anímalos en el orden del documento. - Las líneas continuas largas se ven más naturales; rompe solo donde un bolígrafo real se levantaría.
- Para regiones de color sólido: dibuja primero el trazo del contorno, luego desvanece/crece el relleno detrás (
opacityo un revelado con clip) para que se lea como "coloreado después". - Texto escrito a mano: usa una fuente de trazo/escritura a mano, convierte los glifos en rutas, luego dibuja la ruta de cada glifo en orden de trazo. O simúlalo: enmascara la palabra y barre un revelado de izquierda a derecha con la mano montada en el borde de la máscara.
Activo de la mano marcadora
Un PNG/SVG de una mano sosteniendo un marcador, con el nib en un punto conocido. Mide el desplazamiento del nib una vez (NIB arriba) y reutilízalo. Mantén un activo de mano para toda la pieza para consistencia. Refleja (scaleX(-1)) solo si necesitas una mano izquierda. Agrega una sombra tenue debajo de la mano para profundidad; elimínala durante las pausas cuando la mano está "fuera".
Transiciones de borrado / deslizamiento
Entre secciones, limpia la pizarra para que no se abarrote:
- Deslizar: anima un
clip-path(o un rectángulo blanco) a través del grupo lleno para barrerlo, la mano opcionalmente "borrando". - Dibujo inverso: ejecuta
strokeDashoffset0→len para desdibujar, la mano sigue hacia atrás. - Desvanecer + redibujar: desvanece el grupo antiguo y comienza el dibujo del nuevo grupo.
Mantén una gramática clara (siempre deslizar de izquierda a derecha, por ejemplo) para que las transiciones se sientan deliberadas.
Ritmo para la narración
La duración del dibujo debe seguir la línea hablada, no un temporizador fijo. Estima a partir de la voz en off a ~2.3 palabras/seg; establece el tiempo total de dibujo de cada dibujo aproximadamente a la duración de la línea que ilustra, más una pausa corta. Una etiqueta que la voz en off nombra debe terminar de dibujarse justo cuando se pronuncia la palabra. Ralentiza la mano para líneas de énfasis; acelera trazos de conexión menores. Nunca dejes la mano inactiva en una pizarra terminada mientras la narración continúa — muévela o comienza el siguiente elemento.
Lista de verificación de salida
- Cada trazo es una ruta abierta única en orden de dibujo; nada aparece completamente formado.
- El nib de la mano se sitúa exactamente en la punta del dibujo durante cada trazo (sin flotar, sin retraso).
- La mano se levanta limpiamente entre trazos/elementos separados; sin deslizarse por la pizarra en blanco.
- Los elementos se dibujan en orden de narración, uno a la vez; cada uno se mantiene antes del siguiente.
- El texto escrito a mano se dibuja en orden de lectura; los rellenos aparecen después de su contorno.
- La gramática de borrado/deslizamiento es consistente; la pizarra nunca se sobrecarga.
- El tiempo de dibujo sigue la línea de voz en off que ilustra.
prefers-reduced-motion: muestra la pizarra completada (todos los trazos en offset 0, mano oculta) sin la animación de dibujo.
Entregar y verificar (HTML independiente)
Helper empaquetado (
scripts/):scripts/seek-shot.sh anim.html 0 1.5 3congela el arnés?t=Ny captura cada momento;scripts/contact-sheet.sh sheet.png frame-*.pnglos mosaica para una revisión rápida. Consultascripts/README.md.
Para una escena de pizarra autocontenida (ilustración dibujada, texto escrito a mano, un breve ritmo de scribe), el entregable es un archivo HTML que se abre directamente en un navegador — sin paso de compilación, sin tubería de renderizado. Un solo archivo es el nivel correcto para movimiento web; no recurras a un empaquetador cuando un solo archivo hace el trabajo. (Para un video scribe narrado completo con voz en off incluida, constrúyelo como una composición de Remotion y verifica mediante remotion still — consulta explainer-video).
Contrato de salida:
- Un archivo
.html: SVG en línea (rutas en orden de dibujo,pathLength="1"o longitud medida), el activo de la mano en línea o como data-URI, y el controlador de dibujo en un<script>en línea. - Todo el dibujo en una línea de tiempo maestra (GSAP
tl, o un reloj rAF) para que toda la secuencia tenga un solo cabezal de reproducción que se pueda buscar —strokeDashoffsetY la posición de la mano ambos impulsados por el mismo valor de progreso. - Incluye el arnés de búsqueda a continuación para que cualquier momento pueda congelarse para capturas de pantalla. Congelar en
tmuestra exactamente cuánto del trazo está dibujado — la verificación se basa en esto.
Arnés de búsqueda — congela un momento exacto para capturas de pantalla. ?t=N busca la línea de tiempo maestra a N segundos y la pausa, por lo que una captura de pantalla cae en un fotograma fijo y determinista a medio dibujar.
<script>
// ... construye tu línea de tiempo maestra como `tl` (llamadas drawOn colocadas en ella) ...
const t = new URLSearchParams(location.search).get("t");
if (t !== null) { tl.pause(); tl.seek(parseFloat(t)); } // congelado en t segundos
// sin ?t → reproduce normalmente
window.__ready = true; // señal de listo para espera headless
console.log("duration", tl.duration());
</script>
Bucle de verificación — renderizar → congelar → capturar → comprobar:
- Abre el archivo en momentos a lo largo de la línea de tiempo: inicio, un fotograma a medio dibujar, final:
…/scribe.html?t=0,?t=<dur/2>,?t=<dur>. Leetl.duration()de la consola para el tiempo final. Elige al menos unten medio de un trazo — ahí es donde la ilusión de la mano en la punta se prueba o se rompe. - Captura en headless cada fotograma congelado:
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/scribe.html?t=1.2" frame-mid.png - INSPECCIONA cada fotograma — comprueba fidelidad: en un
ta medio trazo, el nib de la mano se sitúa exactamente en la punta principal de la porción dibujada (no adelante, no atrás, no flotando); la ruta está dibujada solo hasta la punta; en?t=<dur>cada trazo se completa limpiamente sin huecos, sin sobreimpulso, sin dash sobrante. Comprueba artefactos: mano deslizándose por la pizarra en blanco entre trazos, offset de nib incorrecto (la punta flota fuera de la línea), trazos dibujándose fuera de orden, rellenos apareciendo antes de su contorno, dibujos recortados/fuera del lienzo, FOUC antes de que se carguen las fuentes. - Itera: ajusta
NIB, orden de trazo, duraciones, y vuelve a capturar hasta que cada fotograma congelado parezca una mano a medio dibujar.
Antes de terminar:
- Se abre de forma independiente en un navegador — sin errores de consola, sin CDN/activos faltantes.
- Una línea de tiempo maestra;
?t=Ncongela correctamente ystrokeDashoffset+ posición de la mano comparten un valor de progreso. - Capturado en inicio / medio trazo / final — nib de la mano en la punta en todo momento, trazos completados limpiamente al final, orden de dibujo correcto.
- La mano se levanta entre trazos; no se desliza por la pizarra en blanco.
prefers-reduced-motionmuestra la pizarra completada (todos los trazos dibujados, mano oculta) sin la animación de dibujo.
Para una exportación narrada y determinista por fotograma, traslada el mismo dibujo (impulsa strokeDashoffset y la transformación de la mano desde useCurrentFrame()/fps) a una composición de Remotion y renderiza a MP4/GIF — consulta la habilidad explainer-video para el bucle de render-stills → encode.
Archivos de referencia
references/draw-on-recipes.md— mecánicas de dibujo ejecutables:getTotalLength/getPointAtLengthmano-sigue-ruta, variantes de GSAPDrawSVGPluginyMotionPathPlugin, escalonamiento de múltiples trazos, texto escrito a mano por glifo, relleno-después-contorno, transiciones de borrado/deslizamiento y dibujo inverso, y el puerto impulsado por fotogramas de Remotion.references/whiteboard-pipeline.md— convertir arte en SVGs de un solo trazo dibujables, autoría de orden de dibujo, fuentes de escritura a mano/trazo, el activo de la mano marcadora y calibración del nib, ritmo del tiempo de dibujo a la línea de voz en off, y la secuencia de construcción de storyboard a pizarra.