Maquetación Web Dinámica / Canvas y videojuegos 2D
Interacción: teclado y mouse sobre canvas
Hasta ahora tus objetos obedecen a la física, pero no a ti. Esta lección conecta el control humano al canvas: mover una nave con las flechas, apuntar con el mouse, disparar con la barra. Y trae el patrón que separa un movimiento de juego fluido de uno a saltos — el error que casi todos cometen la primera vez.
El error clásico: mover dentro del evento
El patrón correcto: un objeto de teclas
const teclas = {}; // el "tablero de anotaciones": qué está presionado AHORA
// los listeners solo ANOTAN, no mueven nada
canvas.addEventListener("keydown", (e) => { teclas[e.key] = true; });
canvas.addEventListener("keyup", (e) => { teclas[e.key] = false; });
function update() {
// el LOOP lee las anotaciones cada fotograma → movimiento fluido y continuo
if (teclas["ArrowLeft"]) x -= velocidad;
if (teclas["ArrowRight"]) x += velocidad;
if (teclas["ArrowUp"]) y -= velocidad;
if (teclas["ArrowDown"]) y += velocidad;
}Este patrón regala dos cosas gratis: movimiento fluido (sin la demora
de repetición del SO) y múltiples teclas a la vez (arriba + derecha =
diagonal, porque son dos if independientes que se cumplen en el mismo
fotograma). Los nombres de tecla vienen en e.key: "ArrowLeft", " "
(la barra espaciadora es un espacio), "a", "Enter".
Muévela tú
<canvas id="control" width="340" height="200" tabindex="0"></canvas>body { font-family: system-ui; padding: 16px; background: #f8fafc; }
canvas { background: white; border: 1px solid #e2e8f0; border-radius: 12px; cursor: pointer; outline: none; }
canvas:focus { border-color: #6d28d9; box-shadow: 0 0 0 3px #ddd6fe; }const canvas = document.getElementById("control");
const ctx = canvas.getContext("2d");
const teclas = {};
let x = 160, y = 90;
const vel = 3.2, lado = 30;
let enfocado = false;
canvas.addEventListener("focus", () => { enfocado = true; });
canvas.addEventListener("blur", () => { enfocado = false; });
canvas.addEventListener("keydown", (e) => {
teclas[e.key] = true;
if (e.key.startsWith("Arrow")) e.preventDefault(); // que no scrollee la página
});
canvas.addEventListener("keyup", (e) => { teclas[e.key] = false; });
function loop() {
if (teclas["ArrowLeft"]) x -= vel;
if (teclas["ArrowRight"]) x += vel;
if (teclas["ArrowUp"]) y -= vel;
if (teclas["ArrowDown"]) y += vel;
// no dejar salir del canvas
x = Math.max(lado/2, Math.min(canvas.width - lado/2, x));
y = Math.max(lado/2, Math.min(canvas.height - lado/2, y));
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#6d28d9";
ctx.fillRect(x - lado/2, y - lado/2, lado, lado);
if (!enfocado) {
ctx.fillStyle = "#64748b";
ctx.font = "600 14px system-ui";
ctx.textAlign = "center";
ctx.fillText("Haz clic aquí y usa las flechas", canvas.width/2, 24);
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);Haz clic en el canvas (para darle foco) y mueve el cuadro con las flechas
— prueba dos a la vez para la diagonal. El tabindex="0" en el <canvas>
es lo que lo hace enfocable por teclado; sin él no recibiría los eventos.
Y el e.preventDefault() sobre las flechas evita que la página haga
scroll mientras juegas — cortesía obligatoria en juegos reales.
El mouse: coordenadas relativas al canvas
El evento de mouse te da la posición en la VENTANA; para saber dónde cayó
DENTRO del canvas, restas la posición del canvas en pantalla con
getBoundingClientRect():
canvas.addEventListener("mousemove", (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left; // X dentro del canvas
const my = e.clientY - rect.top; // Y dentro del canvas
// ...usa (mx, my): apuntar, arrastrar, dibujar
});Saltarte la resta del rect es el bug clásico del mouse en canvas: el
objetivo aparece desplazado, cada vez más lejos cuanto más abajo está el
canvas en la página. Con la resta, (mx, my) es exacto — la base para
apuntar un cañón, arrastrar una pieza o pintar.
Mueves tu personaje dentro del listener keydown. Al mantener una flecha, da un paso, se congela ~medio segundo y luego corre. ¿Por qué, y cómo se arregla?
Mini-reto
Dale "juice" a tu cuadrado: que deje de ser instantáneo y tenga
INERCIA. En vez de x += vel directo, ten una velocidad vx que aumenta
mientras presionas (vx += 0.5) y se frena sola cuando sueltas
(vx *= 0.9 cada fotograma). El cuadrado ahora "patina" al arrancar y
frenar, como sobre hielo — combinaste el input de hoy con la física de
ayer. Ese detalle es la diferencia entre un control tosco y uno que se
siente bien.
Qué sigue
Tu personaje es un cuadrado de color. La próxima lección le pone cara: sprites y spritesheets — cómo dibujar personajes desde una imagen y animarlos cuadro por cuadro (caminar, saltar) recortando la hoja de dibujos justo a tiempo.