$ cd ..

Así codeamos con Claude · 02: el canvas

11° · 46% · soleado
claudeclaude-codeprocesstinyspritedev-log

Sigue la serie. En el capítulo anterior cerramos con el setup andando. Ahora arrancamos con lo que de verdad importa: el lienzo donde se dibuja.

Cuántos canvases

Antes de codear nada Claude me planteó la primera bifurcación. Para renderizar pixel art tenés dos caminos: usar muchos divs DOM (uno por pixel) o usar un solo <canvas> con la API nativa. La respuesta corta es la segunda. Cien píxeles por cien píxeles a 60fps con divs no rinde, además de los problemas de selección y eventos.

Pero hay una sub-decisión: ¿un canvas o dos? La estrategia que adoptamos es un canvas visible + un canvas offscreen "backing". En el backing dibujás los píxeles a su resolución real (16×16, por ejemplo). Después hacés drawImage(backing, ...) al canvas visible con imageSmoothingEnabled = false, escalado al tamaño que toque. El navegador interpola por vecino más cercano y los píxeles quedan crocantes a cualquier zoom.

El pipeline de render

Cada frame del canvas pasa por la misma secuencia. Ordenada, predecible, fácil de debuggear.

clear borra todo lo del frame anterior. checker dibuja la cuadrícula sutil de transparencia (los dos tonos cozy alternados) solo dentro del área del sprite. pixels mete el bitmap del backing canvas escalado a la zona del sprite. grid dibuja líneas entre celdas, pero solo si el zoom es mayor o igual a 4× (a menos zoom los pixels son chicos y la grilla termina ocultándolos). border cierra con una línea fina alrededor del sprite para que se distinga del fondo.

Cada paso es una función pura que recibe el contexto del canvas y los parámetros. Cero estado escondido. Si algo sale mal, lo aislás con un breakpoint en un paso.

Canvas de TinySprite con un sprite 16x16 vacío visualizado a zoom 4500%, mostrando el patrón checker y la grilla activa
16 × 16 a 4500%. checker, grid y border en su lugar.

Zoom anclado al cursor

Las primeras versiones del zoom suelen escalar desde el centro del canvas. Es la implementación obvia y la que te hace odiar el editor: pasás más tiempo recentrando el sprite que dibujando. La versión buena ancla el cursor: el pixel debajo del mouse antes del zoom queda debajo del mouse después. Cuatro líneas de matemática cambian completamente cómo se siente el editor.

El pan que no tenía que panear

Configuramos pan con space + drag. Estándar de la industria. Funcionaba bien cuando el sprite estaba zoomeado y se salía del viewport. Pero al probarlo a zoom de fit (cuando el sprite entra entero en la pantalla) podías arrastrarlo y dejarlo medio cortado contra un borde, sin razón. Una acción que no resolvía nada y desordenaba la vista.

La regla que aplicamos: si sprite × zoom ≤ container en ambos ejes, no hay nada para panear. El cursor no cambia a grab aunque mantengas space, los pointer events se ignoran, el chip PAN no aparece en la toolbar. El editor reconoce solo cuándo la acción tiene sentido.

Hotkeys que se aprenden solas

Los hotkeys en una app profesional no son opcionales. Pero el problema clásico: nadie los aprende si no los descubre. La solución que probamos: el propio botón es la pista.

En estado normal el botón dice fit o grid. Al hacer hover, el texto se desvanece y aparece centrada una keycap estilo teclado con la tecla. Si lo ves una vez, te queda. Sin paneles de ayuda, sin tooltips llenos de letra chica.

Botones fit y grid en estado normal, mostrando su texto
reposo. el texto del botón a la vista, el atajo escondido.
Botón fit en hover: el texto se reemplazó por una keycap con la letra F y debajo aparece un tooltip que dice Fit sprite to viewport
hover en fit. el texto se desvanece, aparece la keycap F y abajo cae la descripción del comando.
Botón grid en estado activo en hover: keycap con la letra G dentro del botón rosa y tooltip que dice Toggle grid
hover en grid con el botón activo. la keycap se invierte para mantener contraste sobre el fondo rosa.

Por debajo, la infraestructura es platform-aware: si la hotkey tuviera modificador (algo como mod+0 para reset zoom), en Mac mostraría ⌘ 0 y en Windows / Linux Ctrl+0. Una sola fuente de verdad, dos vistas.

Próximo capítulo