Cómo crear vídeos con código: la guía de canvas en Node para no programadores
Un vídeo no es más que muchas imágenes seguidas. Si un programa sabe dibujar una imagen, sabe dibujar un vídeo. Esta guía te explica, sin dar nada por sabido, cómo se hace con @napi-rs/canvas, la librería de Node con la que hemos dibujado una serie entera de diez vídeos.
Por Seryi María · Crestalink · Basado en la versión 1.0.13 de @napi-rs/canvas.
Empecemos por lo raro: los diez vídeos que vas a encontrar a lo largo de este artículo no se grabaron. No hubo cámara, ni editor de vídeo, ni nadie arrastrando cosas en una línea de tiempo. Cada fondo, cada letra, cada círculo y cada subtítulo los pintó un programa, una imagen detrás de otra. Y lo que lo hizo posible es una sola librería que se instala con una orden.
Si no programas, no pasa nada. Aquí no vas a necesitar entender cada línea de código: vas a entender qué pasa cuando se escribe cada línea, que es lo que de verdad importa para saber si esto te sirve, para hablar con quien lo vaya a programar o para dar tus primeros pasos.
1. Qué es un vídeo, visto desde el código
Cuando ves un vídeo en el móvil, tus ojos reciben una imagen fija detrás de otra, tan rápido que el cerebro las une en movimiento. Cada una de esas imágenes se llama fotograma. El cine clásico usa 24 por segundo; los vídeos de esta serie usan 60.
Eso quiere decir que un vídeo de 40 segundos son 2.400 imágenes. Si las tuvieras que dibujar a mano sería imposible, pero un ordenador lo hace sin cansarse: le explicas una vez cómo se dibuja la imagen en función del tiempo («en el segundo 1 el círculo está aquí, en el segundo 2 está un poco más a la derecha») y él pinta las 2.400.
Para eso necesitas dos cosas:
- Algo que sepa dibujar una imagen a partir de órdenes escritas. Eso es canvas, y es de lo que trata este artículo.
- Algo que junte las imágenes en un archivo de vídeo y le ponga el sonido. Esa es la parte final, y la veremos al cierre en De una imagen a un vídeo.
2. Qué es canvas y por qué Node necesita esta librería
Canvas («lienzo» en inglés) es la forma estándar de dibujar con JavaScript. Tu navegador la trae de serie: cuando una web te enseña un gráfico que se mueve, un juego sencillo o un editor de fotos online, muchas veces hay un canvas detrás. Según la documentación de Mozilla (MDN), la API de Canvas sirve para dibujar gráficos con JavaScript y se centra sobre todo en el 2D.
El problema es que, para hacer vídeos en serie, no quieres abrir un navegador. Quieres un programa que se ejecute en tu ordenador o en un servidor, sin ventanas, y que vaya escupiendo imágenes. Ese tipo de programas se escriben con Node.js: JavaScript fuera del navegador.
Y aquí está el detalle: Node no trae canvas. Ahí entra @napi-rs/canvas. Te da en Node el mismo lienzo que tiene el navegador, con las mismas órdenes. Si alguna vez has visto un tutorial de canvas para la web, casi todo te sirve tal cual.
Por dentro, esta librería no pinta «a su manera»: usa Skia, el motor de dibujo de Google que pinta Chrome, ChromeOS, Android y Flutter. Lo que dibujes en Node se verá con la misma calidad que en el navegador más usado del mundo. Lo veremos con más detalle en Por qué es tan rápido.
La idea de todo el proceso cabe en una frase: pides un lienzo, pintas formas, texto o fotos, y lo guardas como imagen. Repite eso 60 veces por segundo y tienes un vídeo.
3. Instalarlo: una orden y nada más
En un proyecto de Node, la librería se instala con una sola orden en la terminal:
npm install @napi-rs/canvasEso es todo. El lema del proyecto lo dice claro: cero dependencias del sistema. Otras librerías de canvas para Node te obligan a instalar antes programas de dibujo del sistema operativo (y en Windows eso suele acabar en una tarde perdida). Esta no: viene ya compilada.
¿Cómo puede ser? Porque el proyecto publica once versiones ya montadas, una por cada tipo de ordenador: Windows (Intel y ARM), Mac (Intel y Apple M), varias de Linux, Android y RISC-V. Cuando haces npm install, npm mira qué ordenador tienes y te baja solo la tuya. Es como pedir unas zapatillas: te traen tu número, no la caja con todas las tallas.
Requisitos que conviene saber. En Linux necesita glibc 2.18 o superior (cualquier distribución moderna lo tiene). En procesadores ARM pide como mínimo un Cortex-A57 (64 bits) o Cortex-A7 (32 bits); en Mac vale cualquier chip M. Para usarla en AWS Lambda, la documentación oficial recomienda añadir una capa de Lambda ya preparada y no meter la librería en el paquete.
4. La hoja y el pincel: createCanvas y getContext
Todo dibujo empieza con dos líneas. Son las mismas en cualquier proyecto, desde una miniatura de YouTube hasta un vídeo de un minuto:
const { createCanvas } = require('@napi-rs/canvas')
const canvas = createCanvas(1080, 1920) // la hoja: 1080 de ancho, 1920 de alto
const ctx = canvas.getContext('2d') // el pincelLa primera línea te da la hoja
createCanvas crea una hoja del tamaño que le pidas, en píxeles. 1080 × 1920 es justo lo que mide la pantalla de un móvil en vertical, el tamaño de un reel o un short. Para YouTube en horizontal usarías 1920 × 1080.
Si no le dices el tamaño, te la da diminuta: 300 × 150. Es el tamaño por defecto del canvas en los navegadores, y la librería lo respeta.
La segunda te da el pincel
getContext('2d') te devuelve lo que los programadores llaman el contexto, y que tú puedes llamar el pincel. Con él tienes casi cincuenta órdenes para pintar: rectángulos, arcos, texto, imágenes, sombras, transparencias…
Lo que hay en la hoja antes de pintar: nada
Mucha gente da por hecho que la hoja empieza en blanco. No: empieza transparente. Si la guardas como PNG sin pintar nada, te sale una imagen vacía, sin fondo. Por eso casi todos los dibujos empiezan pintando un rectángulo que tapa la hoja entera con el color de fondo.
Dónde está el 0,0
Esto es lo que más confunde al principio. En el colegio aprendiste que el punto (0,0) está abajo a la izquierda y que la y crece hacia arriba. En canvas, no:
- El 0,0 está arriba a la izquierda.
- La x crece hacia la derecha.
- La y crece hacia abajo, como cuando lees un libro.
Así que si pintas un cuadrado en (0,0), sale pegado a la esquina de arriba. Y si quieres algo en el centro de una hoja de 1080 × 1920, lo pones en (540, 960).
ctx.fillStyle = '#ff2d95' // elige el color: rosa
ctx.fillRect(0, 0, 200, 200) // cuadrado de 200 px en la esquina de arriba5. Formas, colores y degradados
Con tres cosas dibujas casi todo: un rectángulo, un círculo y un degradado. Lo curioso es que una de las tres ni siquiera existe.
El rectángulo sí existe
fillRect(x, y, ancho, alto) lo pinta relleno de golpe. strokeRect pinta solo el borde. Y si quieres las esquinas redondeadas, existe roundRect.
El círculo no existe
No hay una orden «pinta un círculo». Lo que haces es pedir un arco que da la vuelta entera y luego rellenarlo:
ctx.beginPath() // empiezo un trazo nuevo
ctx.arc(540, 960, 300, 0, Math.PI * 2) // centro, radio, desde 0 hasta la vuelta entera
ctx.fill() // lo rellenoLos ángulos van en radianes, no en grados: Math.PI * 2 es la vuelta completa (360°). Por eso un trozo de pizza es el mismo arco, pero sin cerrar la vuelta: vas del centro al borde, recorres un trozo de arco y vuelves al centro.
Los colores: valen los de la web
Para elegir color, se cambia fillStyle (relleno) o strokeStyle (borde). Vale cualquier forma de escribir colores de la web: un código como '#ff2d95', un nombre como 'red' o incluso nombres tan curiosos como 'rebeccapurple', o 'rgba(255, 0, 0, 0.5)' para un rojo semitransparente.
Ojo: si escribes mal un color ('rosaa', por ejemplo), canvas no te avisa ni da error. Simplemente lo ignora y sigue pintando con el color que tenía antes. Si algo sale de un color que no esperabas, revisa cómo lo has escrito.
El degradado: un color que no se mueve
Lo más raro de los tres es el degradado. No es una forma: es un color que cruza la hoja de un punto a otro.
const degradado = ctx.createLinearGradient(0, 0, 0, 1920) // de arriba a abajo de la hoja
degradado.addColorStop(0, '#ff2d95') // empieza rosa
degradado.addColorStop(1, '#2d6bff') // acaba azul
ctx.fillStyle = degradado
ctx.fillRect(0, 0, 1080, 1920)La trampa es que el degradado está pegado a la hoja, no a la forma. Si pintas un rectángulo pequeño arriba y otro abajo con el mismo degradado, el de arriba sale rosa y el de abajo azul. Si mueves el rectángulo, el degradado no viene con él: cambia de color. Además del lineal, hay degradados radiales (createRadialGradient, en círculos) y cónicos (createConicGradient, que giran como una ruleta).
6. Texto y fuentes propias
Escribir texto parece lo más fácil, y lo es… hasta que tu texto sale con una letra que no es la tuya.
Escribir, en dos líneas
ctx.font = 'bold 96px Montserrat' // tamaño y letra
ctx.fillText('Hola', 100, 300) // la frase y dónde vafont se escribe igual que en las webs: primero el grosor (opcional), luego el tamaño y luego el nombre de la letra. fillText pinta la frase rellena y strokeText solo su contorno. Con measureText puedes saber cuánto va a ocupar una frase antes de pintarla, que es como se centran los títulos o se parten los subtítulos en líneas.
El error que casi todos cometen
Node no conoce tu letra hasta que se la presentas. Y si le pides una que no conoce, no da ningún error: usa otra que sí tiene, y ni te enteras. Es como pedir tu refresco favorito en un bar: si no lo tienen, te ponen otro sin preguntar.
La solución es una línea: registrar el archivo de la fuente con un nombre.
const { GlobalFonts } = require('@napi-rs/canvas')
GlobalFonts.registerFromPath('./fuentes/Montserrat-Bold.ttf', 'Montserrat')
// desde aquí, 'Montserrat' ya vale en ctx.fontGlobalFonts tiene más herramientas útiles:
loadFontsFromDir('./fuentes')registra de golpe todas las fuentes de una carpeta.GlobalFonts.has('Montserrat')te dice si una letra ya está registrada, para comprobarlo antes de pintar.GlobalFonts.familieste lista todas las que conoce.- Entiende fuentes de emojis en color (el README trae un ejemplo con emojis de Apple) y fuentes variables.
Y otro detalle: si no le dices el tamaño, la letra sale de 10 píxeles (el valor por defecto es 10px sans-serif). En una hoja de 1080 × 1920 eso es casi invisible.
Los subtítulos de los vídeos de esta serie están hechos justo así: con una fuente registrada, medida palabra a palabra y pintada en cada fotograma.
Novedad reciente. La versión 1.1.0 (publicada el 30 de octubre de 2026) cambia cómo se pinta el texto por defecto para que salga igual en todos los ordenadores («portable text rendering»). Si actualizas desde la 1.0 y notas el texto distinto, la propia nota de la versión explica cómo volver al modo anterior con setTextRenderer('system').
7. Cargar fotos y combinarlas
Para meter una foto en tu dibujo hacen falta dos pasos: cargarla y pegarla. Y el orden importa más de lo que parece.
No pegues una foto hasta que haya llegado
loadImage no te da la foto: te da una promesa de que llegará. Es como un pedido por internet: hasta que no llama el repartidor, no tienes nada que abrir. Por eso se escribe con await, que significa «espera aquí hasta que llegue»:
const { loadImage } = require('@napi-rs/canvas')
const foto = await loadImage('./fotos/producto.jpg') // espera a que llegue
ctx.drawImage(foto, 0, 0) // y ahora sí, la pegaSi te olvidas del await, intentas pegar algo que todavía no es una foto, y salta un error. Pasa aunque la foto esté en tu propio ordenador: leerla también lleva su tiempo.
De dónde puede venir la foto
A loadImage le vale casi todo: la ruta de un archivo del disco, una dirección de internet (https://...), los bytes de la foto que ya tengas en memoria o incluso un flujo de datos. Entiende PNG, JPEG, WebP, AVIF, GIF y también SVG.
Tres maneras de pegarla
drawImage cambia de comportamiento según cuántos números le pases:
| Números | Qué hace | Ejemplo |
|---|---|---|
| 2 | La pega en ese punto, a su tamaño | drawImage(foto, 100, 100) |
| 4 | La pega en ese punto y del tamaño que digas | drawImage(foto, 100, 100, 500, 500) |
| 8 | Recorta un trozo de la foto y pega solo ese trozo | drawImage(foto, sx, sy, sw, sh, dx, dy, dw, dh) |
(La primera cifra de la orden siempre es la foto; por eso en el vídeo contamos «nueve»: la foto más los ocho números.) Además de fotos, drawImage acepta otro canvas, así que puedes dibujar piezas por separado y luego componerlas.
Lo último que pegas queda encima
Canvas no tiene capas como Photoshop: cada cosa que pintas cae encima de lo anterior, como pegatinas en una libreta. El orden en que escribes las órdenes es el orden de las capas. Fondo primero, personaje después, texto al final.
8. Mover, girar y escalar: el papel, no el dibujo
Esta es la parte que más vuelve loca a la gente, y se arregla cambiando una idea: en canvas no mueves el dibujo, mueves el papel.
Por qué tu cuadrado sale volando
Si pintas un cuadrado en el centro y antes le dices rotate para girarlo, el cuadrado aparece en otra parte de la hoja, o fuera de ella. Es porque rotate no gira el cuadrado: gira el papel entero alrededor del 0,0, que está en la esquina de arriba.
Imagínalo como un folio clavado en la mesa con una chincheta en la esquina: si lo giras, todo da vueltas alrededor de la chincheta. La solución es mover antes la chincheta al centro del cuadrado:
ctx.translate(540, 960) // muevo el 0,0 al centro de la hoja
ctx.rotate(Math.PI / 4) // giro el papel 45° (en radianes)
ctx.fillRect(-100, -100, 200, 200) // cuadrado centrado en el nuevo 0,0Escalar y hacer espejo
scale(2, 2) hace que todo lo que pintes después salga al doble de tamaño. Y con un número negativo, le das la vuelta: scale(-1, 1) es un espejo horizontal, útil para que un personaje mire al otro lado sin dibujarlo dos veces.
Guardar y devolver el papel
El problema: el papel se queda movido para todo lo que pintes después. Por eso existen save y restore. save apunta cómo estaba el papel (su posición, giro, tamaño, y también el color y la letra elegidos); restore lo devuelve exactamente a ese punto.
ctx.save() // apunto cómo está el papel
ctx.translate(540, 960)
ctx.rotate(angulo)
ctx.fillRect(-100, -100, 200, 200)
ctx.restore() // y lo dejo como estabaEn un vídeo esto se usa constantemente: cada objeto que se mueve o gira va entre un save y un restore, y así no afecta a los demás.
9. Path2D: formas que se guardan, se funden y se recortan
Hasta ahora, cada forma se dibujaba y desaparecía de la memoria. Path2D cambia eso: es un camino que guardas para usarlo las veces que quieras, como un molde.
const { Path2D } = require('@napi-rs/canvas')
const corazon = new Path2D('M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z')
ctx.fill(corazon)Esa cadena rara de letras y números es el formato de los dibujos SVG, el de los logos e iconos de la web. Path2D lo entiende tal cual, así que puedes coger el icono de tu marca, copiar su «camino» y dibujarlo en canvas.
Lo que el navegador no tiene: PathKit
Aquí @napi-rs/canvas va más allá del canvas del navegador. Trae herramientas de Skia (llamadas PathKit) para operar con formas:
Unir, cruzar y restar con op
const { PathOp } = require('@napi-rs/canvas')
const juntas = formaA.op(formaB, PathOp.Union) // las dos en una sola
const comun = formaA.op(formaB, PathOp.Intersect) // solo lo que tienen en común
const hueco = formaA.op(formaB, PathOp.Difference) // A con el hueco de BLas cinco operaciones que hay:
| Operación | Qué deja |
|---|---|
Union | Las dos formas fundidas en una |
Intersect | Solo la zona donde se tocan |
Difference | La primera, con el hueco de la segunda |
ReverseDifference | La segunda, con el hueco de la primera |
Xor | Todo menos la zona donde se tocan |
Restar es como un molde de galletas: aprietas sobre la masa, y a la masa le queda el hueco.
Para curiosos: en el README oficial, la lista que describe estas operaciones está descolocada (a Union le pone la descripción de Difference, y así sucesivamente). La buena es la de los tipos de la librería (index.d.ts), que es la que recoge esta tabla.
Dibujar solo un trozo con trim
Lo más raro, y lo más bonito para vídeo: trim(inicio, fin) se queda solo con un trozo del camino, contado de 0 a 1. trim(0, 0.5) es la primera mitad. Si en cada fotograma subes ese número un poquito (0,01; 0,02; 0,03…), el trazo se dibuja solo, como una firma que se escribe delante de ti.
Y más herramientas
simplify(): limpia una forma con trozos que se solapan.round(radio): redondea las esquinas de cualquier forma.dash(on, off, fase): la convierte en línea discontinua.stroke(): convierte un borde en una forma rellenable.toSVGString(): te devuelve el resultado como texto SVG, para usarlo en otra parte.getBounds(): te dice cuánto ocupa la forma, para centrarla.
En resumen: un camino no es un dibujo, es un molde que guardas, mezclas, recortas y vuelves a usar.
10. Píxeles a mano: una foto es una lista de números
Debajo de todo dibujo hay números. Y canvas te deja tocarlos uno a uno.
Las cuentas
- Una hoja de 1080 × 1920 tiene 2.073.600 píxeles.
- Cada píxel son 4 números: rojo, verde, azul y transparencia (lo que se llama RGBA).
- Cada número va de 0 a 255.
- Total: más de 8 millones de números (8.294.400) en una sola imagen.
Si cada número fuera un grano de arroz, tendrías más de cien kilos de arroz. Por cada fotograma.
Leer, cambiar y volver a pintar
getImageData te da esa lista entera, todos los números en fila. Los cambias como quieras, y putImageData los pinta de vuelta. Por ejemplo, para poner una foto en negativo, como los carretes de antes, basta con restar cada color de 255:
const datos = ctx.getImageData(0, 0, 1080, 1920)
const n = datos.data // la lista: R, G, B, A, R, G, B, A...
for (let i = 0; i < n.length; i += 4) {
n[i] = 255 - n[i] // rojo
n[i + 1] = 255 - n[i + 1] // verde
n[i + 2] = 255 - n[i + 2] // azul
// n[i + 3] es la transparencia: la dejamos igual
}
ctx.putImageData(datos, 0, 0)Un detalle práctico: los números no se salen del rango. Si intentas poner 300, se queda en 255; si pones −20, se queda en 0. Así no rompes la imagen aunque te pases en una cuenta.
Con esto se hacen filtros (blanco y negro, sepia, más contraste), efectos de «glitch», máscaras o análisis de color. Es la parte más lenta de canvas, porque son millones de operaciones; para efectos sencillos suele ser mejor ctx.filter (por ejemplo 'grayscale(1)' o 'blur(8px)'), que lo hace Skia por dentro.
11. Guardar la imagen: PNG, JPEG, WebP, AVIF, GIF, SVG y PDF
Lo que pintas vive en memoria. Para tenerlo en un archivo, hay que codificarlo en un formato. En @napi-rs/canvas eso es una línea:
const { writeFile } = require('node:fs/promises')
const png = await canvas.encode('png') // también 'jpeg', 'webp', 'avif' y 'gif'
await writeFile('miniatura.png', png)PNG o JPEG: uno pesa seis veces más
Con la misma foto de 1080 × 1920, en nuestras pruebas el PNG pesó 1.286 KB y el JPEG, 196 KB. ¿Por qué tanta diferencia?
- JPEG tira detalles que tu ojo no ve. Pesa poco, pero pierde algo de calidad cada vez.
- PNG los guarda todos, sin perder nada. Pesa más con fotos… pero con un dibujo de colores lisos (un gráfico, un logo, un texto sobre fondo plano) gana: pesa menos y no emborrona los bordes. Además guarda la transparencia, y JPEG no.
Todos los formatos
| Formato | Para qué | Cómo |
|---|---|---|
| PNG | Dibujos, logos, texto, transparencias, fotogramas de vídeo sin pérdida | encode('png') |
| JPEG | Fotos, miniaturas que tienen que pesar poco | encode('jpeg', 90) (calidad 0–100) |
| WebP | Imágenes para web: pesan todavía menos | encode('webp', 80) |
| AVIF | El más moderno y el que menos pesa; tarda más en guardarse | encode('avif') |
| GIF | Imagen suelta en GIF, o animaciones con GifEncoder | encode('gif') |
| SVG | Dibujo vectorial que se amplía sin perder calidad | createCanvas(ancho, alto, SvgExportFlag...) |
| Documentos de varias páginas | new PDFDocument() |
No bloquea tu programa
encode trabaja en segundo plano (en el «thread pool» de Node, según el README): tu programa sigue mientras se guarda la imagen, como el horno que hornea mientras tú recoges la cocina. Cuando vas a guardar miles de fotogramas, esto se nota. Si prefieres que espere, existe encodeSync, y también toBuffer('image/png'), que es la forma que usa el canvas del navegador.
Extras que casi nadie usa (y deberían)
- Metadatos: en PNG y JPEG puedes guardar dentro del archivo el título, el autor, el copyright o los DPI (por ejemplo,
encode('jpeg', { quality: 90, copyright: '© 2026 Crestalink' })). - GIF animados:
GifEncoderva sumando fotogramas con su duración y al final te da el GIF entero. - PDF:
PDFDocumentte da una página con su pincel; dibujas en ella como en un canvas y pasas a la siguiente.
¿Cuál es el mejor formato? No hay uno: las fotos, en JPEG (o WebP/AVIF para web); los dibujos, en PNG.
12. Por qué es tan rápido: Skia, Rust y Node-API
Parece una librería pequeña de Node, pero dentro lleva el mismo motor que dibuja Chrome. Y cabe en un npm install. Veamos las tres piezas.
1. Skia: el motor
Skia es una biblioteca de gráficos 2D de código abierto, patrocinada y mantenida por Google. Es el motor de dibujo de Chrome, ChromeOS, Android y Flutter. Cada vez que ves una web en Chrome o abres una app en un Android, Skia está pintando. @napi-rs/canvas incluye el Skia de Chrome 157, de los más recientes.
2. Rust: el pegamento
Skia está escrito en C++, y Node habla JavaScript. Hace falta algo en medio que traduzca. Esa parte está escrita en Rust, un lenguaje conocido por ser rápido y seguro con la memoria, usando NAPI-RS, una herramienta para crear complementos de Node en Rust y publicarlos ya compilados. El autor de ambos proyectos es el mismo: el desarrollador LongYinan (@Brooooooklyn en GitHub).
3. Node-API: la puerta oficial
Lo que une Rust con Node entra por Node-API, la puerta oficial que el propio Node mantiene para complementos nativos. Su gran ventaja es que es estable entre versiones de Node: un complemento hecho con Node-API sigue funcionando cuando actualizas Node, sin volver a compilarlo. Por eso esta librería puede publicarse ya montada en esas once versiones y funcionar sin nada más en tu ordenador.
La ficha del proyecto
| Paquete | @napi-rs/canvas («skr canvas» en su README) |
|---|---|
| Repositorio | github.com/Brooooooklyn/canvas (unas 2.400 estrellas) |
| Licencia | MIT: libre, también para uso comercial |
| Motor | Skia (Chrome m157) |
| Dependencias del sistema | Ninguna |
| Versiones montadas | 11 (Windows, macOS, Linux glibc y musl, ARM, Android, RISC-V) |
| Última versión | 1.1.0, del 30 de octubre de 2026 (esta guía se escribió con la 1.0.13) |
13. De una imagen a un vídeo
Ya sabes dibujar una imagen. Un vídeo es lo mismo, repetido, cambiando un poco cada vez. Este es el esqueleto de cualquier vídeo hecho con código:
const { createCanvas } = require('@napi-rs/canvas')
const { writeFile, mkdir } = require('node:fs/promises')
const canvas = createCanvas(1080, 1920)
const ctx = canvas.getContext('2d')
const fps = 60 // fotogramas por segundo
const segundos = 3
await mkdir('fotogramas', { recursive: true })
for (let i = 0; i < fps * segundos; i++) {
const t = i / fps // el tiempo de este fotograma, en segundos
// 1. fondo
ctx.fillStyle = '#0d0b14'
ctx.fillRect(0, 0, 1080, 1920)
// 2. un círculo que cruza la pantalla en 3 segundos
const x = 100 + (880 * t) / segundos
ctx.fillStyle = '#ff2d95'
ctx.beginPath()
ctx.arc(x, 960, 120, 0, Math.PI * 2)
ctx.fill()
// 3. guardar el fotograma: 0000.png, 0001.png...
const nombre = String(i).padStart(4, '0') + '.png'
await writeFile('fotogramas/' + nombre, await canvas.encode('png'))
}La clave está en la variable t: todo lo que se dibuja depende del tiempo. En el fotograma 0 el círculo está a la izquierda; en el 180, a la derecha. Entre medias, se mueve solo. Con todo lo de este artículo (formas, texto, fotos, giros, trazos que se dibujan con trim, filtros de píxeles) puedes hacer que cualquier cosa cambie con t.
Esos 180 PNG son ya tu vídeo, solo falta empaquetarlo. Para eso se usa un programa aparte que une imágenes en un MP4 y les pone el audio (el más conocido es ffmpeg). Esa parte ya no es canvas, así que la dejamos aquí.
Dos atajos oficiales
- Animaciones Lottie. Si un diseñador te pasa una animación de After Effects exportada a Lottie (un archivo
.json), @napi-rs/canvas la reproduce conLottieAnimation, usando Skottie, el reproductor de Lottie de Skia. ConseekFrame(n)vas al fotograma que quieras y conrender(ctx)lo pintas en tu lienzo, encima o debajo de lo que tú dibujes. - Codificar a MP4 sin salir de Node. El repositorio oficial trae un ejemplo (
example/lottie-to-video.ts) que pasa una animación Lottie a MP4 con otra librería del mismo autor,@napi-rs/webcodecs.
Así se dibujó la serie entera de este artículo: los diez vídeos, fotograma a fotograma, con las mismas órdenes que has visto aquí.
14. Los errores que casi todos cometen
Lo bueno de canvas es que casi nunca se rompe. Lo malo es que, cuando algo va mal, muchas veces no avisa. Esta es la lista de los fallos silenciosos que salen en la serie:
| Lo que pasa | Por qué | Cómo se arregla |
|---|---|---|
| La imagen sale sin fondo | La hoja empieza transparente | Pinta un rectángulo de fondo lo primero |
| Algo sale en la esquina de arriba | El 0,0 está arriba a la izquierda | Recuerda: la y crece hacia abajo |
| Un color no cambia | Está mal escrito y se ignora | Revisa el nombre o el código del color |
| El degradado cambia al mover la forma | El degradado está pegado a la hoja | Calcula el degradado desde la posición de la forma |
| El texto sale con otra letra | La fuente no está registrada | GlobalFonts.registerFromPath antes de pintar |
| El texto casi no se ve | Sin tamaño, sale de 10 px | Pon siempre el tamaño en font |
| Error al pegar una foto | No se esperó a que cargara | await loadImage(...) |
| Lo que giras sale volando | Se gira el papel alrededor del 0,0 | translate al centro, luego rotate |
| Todo lo de después sale girado | El papel se quedó movido | Envuelve con save y restore |
| Los archivos pesan muchísimo | Fotos guardadas en PNG | Fotos en JPEG/WebP, dibujos en PNG |
15. Glosario rápido
- Canvas (lienzo)
- La hoja donde se dibuja con código, y el conjunto de órdenes para hacerlo.
- Contexto (
ctx) - El «pincel»: el objeto que tiene todas las órdenes de dibujo.
- Píxel
- Cada uno de los puntitos de una imagen; cada uno son 4 números (RGBA).
- Fotograma
- Cada imagen fija de un vídeo. 60 por segundo en esta serie.
- Node.js
- Programa que ejecuta JavaScript fuera del navegador.
- npm
- La tienda de librerías de Node.
npm installbaja una. - Promesa /
await - Algo que llegará más tarde, y la orden de esperarlo.
- Radianes
- La unidad de los ángulos en canvas:
Math.PI * 2es una vuelta entera. - SVG
- Formato de dibujo con formas en vez de píxeles; se amplía sin perder calidad.
- Codificar (
encode) - Convertir lo pintado en un archivo de imagen (PNG, JPEG…).
- Skia
- El motor de dibujo de Google que hay dentro de Chrome, Android, Flutter y esta librería.
- Node-API
- La puerta oficial para conectar Node con programas escritos en otros lenguajes.
16. Preguntas frecuentes
¿Necesito saber programar para hacer vídeos con código?
¿Es gratis @napi-rs/canvas?
¿Qué diferencia hay con node-canvas?
¿Funciona en Windows, Mac y Linux?
¿Puedo usarlo en un servidor o en AWS Lambda?
¿Sirve solo para vídeos?
¿Puede hacer 3D?
17. Fuentes
- @napi-rs/canvas en npm: documentación oficial (README) y sus tipos
index.d.ts, versión 1.0.13. - Repositorio oficial en GitHub, con sus ejemplos y notas de cada versión.
- Skia: qué es y quién lo usa y Skottie, el reproductor de Lottie de Skia.
- NAPI-RS: complementos de Node escritos en Rust.
- Documentación de Node-API (Node.js).
- MDN: API de Canvas y su tutorial, la referencia de las órdenes estándar.
- Canvas-Lambda-Layer, la capa para AWS Lambda que recomienda el README.
Esta guía tiene diez vídeos cortos, uno por cada parte, para verla en un minuto cada una. Si quieres aprender más, sígueme en @SeryiMariaCrestalink y no te pierdas nada.
Relacionados

Cómo Comprar Enlaces de Calidad sin Riesgos

La relación entre SEO On-Page y el éxito de tu campaña de Link Building

Backlinks permanentes que fortalecen tu presencia online y tu visibilidad en buscadores

Cómo una web puede subir hasta 15 posiciones para lograr un mayor crecimiento orgánico

El proceso de crestalink: Cómo creamos enlaces manuales que Google adora

¿Qué es el Guest Posting y por qué es clave para el SEO de autoridad?

La autoridad de dominio como métrica clave para optimizar tu Link Building

Cómo comprar enlaces para e-commerce puede favorecer tu estrategia de ventas online


