Lección 14 de 1512 min
Gráficos y multimedia
Canvas, WebGL y WebGPU para dibujar; Web Audio, MediaDevices, MediaRecorder y Web Speech para sonido, cámara y voz.
Un juego que corre en una pestaña, un visor 3D para girar una zapatilla antes de comprarla, una videollamada sin instalar nada, una nota de voz que se graba y se sube desde el navegador: todo eso se hace con APIs que el navegador trae de serie.
Esta lección recorre las dos familias: las que dibujan píxeles en pantalla (Canvas, WebGL y WebGPU) y las que trabajan con sonido, cámara y voz. No vas a salir escribiendo shaders, pero sí sabiendo qué pieza elegir para cada problema.
Dibujar en la página
HTML y CSS describen documentos, y SVG describe formas que forman parte del DOM. Cuando necesitas dibujar miles de cosas que cambian 60 veces por segundo, lo práctico es un elemento <canvas>: un lienzo de píxeles sobre el que pintas con JavaScript. La diferencia entre las tres APIs es quién hace el trabajo y cuánto control tienes sobre la tarjeta gráfica (GPU).
Canvas API (2D)
Un lienzo de píxeles en el que dibujas formas, texto e imágenes con JavaScript.
- Soporte:
- Todos los navegadores
- Modo:
- Inmediato (píxeles)
Obtienes un contexto con canvas.getContext("2d") y le das órdenes: fillRect() para un rectángulo, arc() para un círculo, fillText() para texto, drawImage() para pegar una imagen. También puedes leer y modificar los píxeles uno a uno con getImageData().
Es un modo inmediato: el lienzo no recuerda qué dibujaste, solo guarda los píxeles resultantes. Para animar, borras y vuelves a pintar cada fotograma dentro de requestAnimationFrame().
Úsalo cuando
Para gráficas con muchos datos, juegos 2D, firmas a mano alzada, editores de imagen sencillos o para generar una imagen y descargarla.
Ten en cuenta
Lo que pintas son píxeles: un lector de pantalla no ve nada dentro, así que añade un texto alternativo o una tabla con los datos. En pantallas de alta densidad se ve borroso si no ajustas el tamaño con devicePixelRatio. Para unos pocos iconos o diagramas, SVG es más sencillo y accesible.
const lienzo = document.querySelector("canvas");
const ctx = lienzo.getContext("2d");
const ventas = [120, 80, 150, 60];
ctx.fillStyle = "#7c3aed";
ventas.forEach((valor, i) => {
ctx.fillRect(20 + i * 50, 180 - valor, 40, valor); // x, y, ancho, alto
});
ctx.fillStyle = "#18181b";
ctx.font = "16px sans-serif";
ctx.fillText("Ventas por trimestre", 20, 24);WebGL
Gráficos 3D acelerados por la GPU, con una API basada en OpenGL ES.
- Shaders:
- GLSL
- Basado en:
- OpenGL ES
WebGL lleva al navegador OpenGL ES, la API gráfica de los móviles. Tú envías a la GPU listas de vértices (los puntos que forman triángulos) y pequeños programas llamados shaders, escritos en GLSL, que calculan la posición y el color de cada píxel en paralelo. Así se consiguen escenas 3D fluidas.
La API es de muy bajo nivel: dibujar un triángulo requiere decenas de líneas. Por eso casi todo el mundo usa una librería como Three.js, Babylon.js o, para 2D muy rápido, PixiJS. WebGL 2 es la versión actual y funciona en todos los navegadores principales.
Úsalo cuando
Para visores 3D de productos, juegos, mapas interactivos y visualizaciones con decenas de miles de elementos que deben moverse con fluidez.
Ten en cuenta
Sin librería, la curva de aprendizaje es muy dura. El navegador puede perder el contexto de la GPU (por ejemplo, si el sistema reinicia el controlador gráfico) y tu código debe saber recuperarse. Y los modelos 3D pesan: optimízalos y cárgalos con calma.
import * as THREE from "three";
const escena = new THREE.Scene();
const camara = new THREE.PerspectiveCamera(60, ancho / alto, 0.1, 100);
const render = new THREE.WebGLRenderer({ canvas: lienzo });
const cubo = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshNormalMaterial(),
);
escena.add(cubo);
camara.position.z = 3;
render.setAnimationLoop(() => {
cubo.rotation.y += 0.01;
render.render(escena, camara);
});WebGPU
La API moderna para la GPU: gráficos más eficientes y cálculo de propósito general.
- Shaders:
- WGSL
- Novedad:
- Compute shaders
WebGPU es la sucesora de WebGL. Está diseñada a imagen de las APIs gráficas actuales (Vulkan, Metal y Direct3D 12), lo que le permite aprovechar mejor la GPU y gastar menos tiempo de CPU en cada fotograma. Sus shaders se escriben en un lenguaje nuevo, WGSL.
Su gran novedad son los compute shaders: programas que usan la GPU para calcular, no para dibujar. Eso abre la puerta a ejecutar modelos de inteligencia artificial, simulaciones físicas o procesado de vídeo dentro del navegador a mucha más velocidad que en la CPU.
Úsalo cuando
Para 3D exigente, para ejecutar modelos de IA en el navegador o para cualquier cálculo masivo y paralelo. Three.js y Babylon.js ya pueden usarla por debajo.
Ten en cuenta
Es más compleja aún que WebGL. Chromium la tiene desde hace tiempo y Safari y Firefox la han ido activando en versiones recientes, en algunos casos solo en ciertos sistemas: comprueba navigator.gpu y ten WebGL como plan B.
const adaptador = await navigator.gpu?.requestAdapter();
if (!adaptador) {
usarWebGL(); // plan B
} else {
const gpu = await adaptador.requestDevice();
const ctx = lienzo.getContext("webgpu");
ctx.configure({ device: gpu, format: navigator.gpu.getPreferredCanvasFormat() });
// Los shaders se escriben en WGSL
const modulo = gpu.createShaderModule({
code: "@fragment fn main() -> @location(0) vec4f { return vec4f(0.5, 0.2, 0.9, 1.0); }",
});
}Nota
Las tres dibujan sobre el mismo elemento <canvas>. Lo que cambia es el contexto que pides con getContext(): "2d", "webgl2" o "webgpu".
Sonido y voz
Web Audio API
Genera, mezcla y analiza sonido con precisión de milisegundos.
- Soporte:
- Todos los navegadores
- Modelo:
- Grafo de nodos
Todo empieza con un AudioContext. Dentro creas nodos y los conectas como los cables de una mesa de mezclas: una fuente (un oscilador, un archivo de sonido o el micrófono), efectos (volumen, filtros, eco, panorámica) y el destino final, los altavoces (ctx.destination).
Puedes programar cada sonido en un instante exacto con ctx.currentTime y analizar frecuencias en tiempo real con un AnalyserNode, que es lo que mueve las barras de un visualizador de música.
Úsalo cuando
Para efectos de sonido en juegos, sintetizadores, editores de audio, afinadores y visualizadores. Para reproducir una canción o un pódcast sin más, basta con <audio>.
Ten en cuenta
Por las políticas de reproducción automática, el contexto empieza en pausa ("suspended") hasta que el usuario interactúa con la página: llama a ctx.resume() dentro de un clic.
const audio = new AudioContext();
botonPitido.addEventListener("click", async () => {
await audio.resume(); // empieza en pausa hasta un gesto del usuario
const oscilador = audio.createOscillator();
const volumen = audio.createGain();
oscilador.frequency.value = 440; // la nota la (A4)
volumen.gain.value = 0.2;
oscilador.connect(volumen).connect(audio.destination);
oscilador.start();
oscilador.stop(audio.currentTime + 0.3); // 300 ms exactos
});Web Speech API
Convierte texto en voz y, con soporte más limitado, voz en texto.
- Síntesis:
- Amplio soporte
- Reconocimiento:
- Limitado
Tiene dos mitades. La síntesis (speechSynthesis) lee un texto en voz alta con las voces instaladas en el sistema: creas un SpeechSynthesisUtterance, eliges idioma, velocidad y tono, y llamas a speak(). Funciona en todos los navegadores principales.
El reconocimiento (SpeechRecognition) escucha el micrófono y devuelve el texto transcrito. Su soporte es mucho más irregular: Chrome y Safari lo ofrecen, a menudo con el prefijo webkit, y Firefox no. En Chrome, el audio suele enviarse a un servidor externo para transcribirlo.
Úsalo cuando
La síntesis, para leer instrucciones en voz alta o mejorar la accesibilidad. El reconocimiento, para búsquedas por voz o dictado como mejora opcional.
Ten en cuenta
Las voces disponibles dependen del sistema y se cargan de forma asíncrona (escucha voiceschanged). No construyas una función esencial solo sobre el reconocimiento: detecta si existe y ofrece siempre la opción de escribir.
// Síntesis: leer en voz alta (amplio soporte)
const frase = new SpeechSynthesisUtterance("Precalienta el horno a 180 grados");
frase.lang = "es-ES";
frase.rate = 0.9;
speechSynthesis.speak(frase);
// Reconocimiento: soporte limitado, compruébalo antes
const Reconocimiento = window.SpeechRecognition ?? window.webkitSpeechRecognition;
if (Reconocimiento) {
const oido = new Reconocimiento();
oido.lang = "es-ES";
oido.onresult = (e) => buscar(e.results[0][0].transcript);
oido.start();
}Cámara, micrófono y grabación
Acceder a la cámara o al micrófono es de lo más delicado que puede hacer una web, así que siempre requiere HTTPS y un permiso explícito, y el navegador muestra un indicador mientras están en uso.
MediaDevices API
Pide acceso a la cámara y al micrófono y lista los dispositivos disponibles.
- Requiere:
- HTTPS y permiso
- Devuelve:
- MediaStream
navigator.mediaDevices.getUserMedia({ video: true, audio: true }) pide permiso y devuelve un MediaStream: un flujo en directo con pistas de vídeo y de audio. Lo muestras asignándolo a video.srcObject, o lo envías a otra persona en una videollamada con WebRTC.
enumerateDevices() devuelve la lista de cámaras, micrófonos y altavoces, para que el usuario elija cuál usar. Y getDisplayMedia() captura la pantalla o una ventana, como al compartir pantalla en una reunión.
Úsalo cuando
Para videollamadas, escanear códigos QR, hacer una foto de perfil, dictar notas o compartir pantalla.
Ten en cuenta
Hasta que el usuario concede el permiso, enumerateDevices() devuelve los dispositivos sin nombre: es una protección contra el rastreo. Y apaga siempre las pistas con pista.stop() al terminar, o el indicador de cámara seguirá encendido.
const flujo = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "user", width: { ideal: 1280 } },
audio: true,
});
document.querySelector("video").srcObject = flujo;
// Con el permiso concedido, ya ves los nombres de los dispositivos
const dispositivos = await navigator.mediaDevices.enumerateDevices();
const camaras = dispositivos.filter((d) => d.kind === "videoinput");
console.log(camaras.map((c) => c.label));
// Al terminar, apaga cámara y micrófono
flujo.getTracks().forEach((pista) => pista.stop());MediaRecorder API
Graba un flujo de audio o vídeo y lo convierte en un archivo.
- Entrada:
- Un MediaStream
- Salida:
- Un Blob
new MediaRecorder(flujo) graba cualquier MediaStream: el de la cámara, el del micrófono, el de la pantalla o incluso el de un <canvas>. Llamas a start(), el navegador va entregando trozos de datos en el evento dataavailable y, al llamar a stop(), los juntas en un Blob que puedes reproducir, descargar o subir.
Úsalo cuando
Para notas de voz, grabar un vídeo corto de presentación, guardar una partida de un juego en canvas o grabar la pantalla para un tutorial.
Ten en cuenta
Cada navegador graba en formatos distintos (WebM, MP4…): pregunta con MediaRecorder.isTypeSupported() antes de elegir. Los vídeos largos ocupan mucha memoria; para grabaciones largas, sube los trozos a medida que llegan.
const flujo = await navigator.mediaDevices.getUserMedia({ audio: true });
const tipo = MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
const grabadora = new MediaRecorder(flujo, { mimeType: tipo });
const trozos = [];
grabadora.addEventListener("dataavailable", (e) => trozos.push(e.data));
grabadora.addEventListener("stop", () => {
subirNotaDeVoz(new Blob(trozos, { type: tipo }));
flujo.getTracks().forEach((pista) => pista.stop());
});
grabadora.start();
setTimeout(() => grabadora.stop(), 30_000); // 30 segundos¿Sabías que…?
Las videollamadas en el navegador combinan tres piezas: getUserMedia para capturar, WebRTC para enviar el flujo a la otra persona con muy poco retraso y, si se graba la reunión, MediaRecorder.
Interactivo
Canvas 2D, WebGL y WebGPU
Las tres pintan sobre un <canvas>, pero con niveles de control y de dificultad muy distintos.
| Característica | Canvas 2D | WebGL | WebGPU |
|---|---|---|---|
| Cómo dibujas | Órdenes de alto nivel: rectángulos, trazos, texto, imágenes | Triángulos y shaders en GLSL | Pipelines y shaders en WGSL |
| Uso de la GPU | La decide el navegador; tú no la controlas | Sí, directamente | Sí, con más control y menos coste de CPU |
| Cálculo general | No | Solo con trucos | Sí, con compute shaders |
| Dificultad | Baja | Alta; casi siempre con una librería | Muy alta; casi siempre con una librería |
| Soporte | Todos los navegadores | Todos los navegadores (WebGL 2 incluido) | Versiones recientes; compruébalo antes |
| Uso típico | Gráficas, juegos 2D, editar imágenes | Visores y juegos 3D, mapas | 3D exigente e IA en el navegador |
Ponlo en práctica
Saber qué es cada cosa es la mitad del trabajo. La otra mitad es reconocer cuándo usarla.
Interactivo
¿Qué usarías?
Lee cada situación y elige la opción que mejor la resuelve. Fallar aquí no cuenta: es para practicar.
En una app de reparto, el cliente debe firmar con el dedo en la pantalla al recibir el paquete.
Para recordar
- Canvas 2D, WebGL y WebGPU pintan sobre un
<canvas>: elige la más sencilla que resuelva tu problema. - WebGL funciona en todas partes; WebGPU es más potente y permite cálculo general, pero comprueba su soporte y ten un plan B.
- Lo que pintas en un canvas son píxeles: añade siempre una alternativa accesible.
- Cámara y micrófono exigen HTTPS y permiso;
getUserMediacaptura,MediaRecordergraba y Web Audio procesa el sonido. - La síntesis de voz funciona en todos los navegadores principales; el reconocimiento no, así que úsalo solo como mejora.
Comprueba lo aprendido
Cuestionario
Responde las 5 preguntas para completar la lección.
1.Has hecho una gráfica de ventas con un canvas 2D. ¿Qué problema tiene para un usuario con lector de pantalla?
2.Creas un
AudioContextal cargar la página y no suena nada. ¿Qué pasa?3.
enumerateDevices()te devuelve tres cámaras, pero todas con el nombre vacío. ¿Por qué?4.¿Qué aporta WebGPU que WebGL no tiene?
5.Quieres añadir búsqueda por voz a tu web. ¿Cuál es el enfoque correcto?