javascript

1. Eventos en JavaScript

Un evento es cualquier cosa que ocurre en el navegador: un click, mover el mouse, hacer scroll, redimensionar la ventana, presionar una tecla... JavaScript puede "escuchar" esos eventos con addEventListener y ejecutar una función de respuesta.

// ── SINTAXIS BÁSICA ───────────────────────────────────────────
// elemento.addEventListener(tipo, callback, opciones?)

const boton = document.querySelector('#mi-boton');

boton.addEventListener('click', function(event) {
  console.log('¡Clic!', event);
});

// Con arrow function (más conciso):
boton.addEventListener('click', (e) => {
  e.preventDefault(); // cancela el comportamiento por defecto
  e.stopPropagation(); // detiene la propagación hacia padres
});

// ── REMOVER UN LISTENER ──────────────────────────────────────
// IMPORTANTE: para poder quitar un listener, la función
// debe ser referenciada (no inline anónima).
const manejador = (e) => console.log(e);
boton.addEventListener('click', manejador);
boton.removeEventListener('click', manejador); // ← quita el listener

Eventos más usados

EventoCuándo se disparaPropiedades útiles del Event
clickAl hacer click con el mouseclientX, clientY, target, button
mousemoveAl mover el cursorclientX, clientY, movementX, movementY
scrollAl hacer scroll en la ventana o elementowindow.scrollY, scrollTop
resizeAl cambiar el tamaño de la ventanawindow.innerWidth, innerHeight
keydownAl presionar una teclakey, code, ctrlKey, shiftKey
keyupAl soltar una teclakey, code
inputAl cambiar el valor de un inputtarget.value
DOMContentLoadedAl terminar de cargar el HTML— (sin datos relevantes)

Demo — click

addEventListener('click', ...)

Esperando eventos de click...

Demo — mousemove

addEventListener('mousemove', ...) — mueve el cursor sobre esta área

x: — · y: —
Mueve el cursor sobre la zona de arriba...

Demo — scroll

window.addEventListener('scroll', ...) — desplázate por la página

Progreso de scroll en la página:
Desplázate por la página para ver el evento scroll...
// ── MOUSEMOVE: coordenadas normalizadas ───────────────────────
// Para Three.js, necesitamos valores entre -1 y +1 (NDC)
const mouse = { x: 0, y: 0 };

window.addEventListener('mousemove', (e) => {
  // normalizar: -1 (izquierda/abajo) a +1 (derecha/arriba)
  mouse.x = (e.clientX / window.innerWidth)  * 2 - 1;
  mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
});

// ── SCROLL: porcentaje de la página ──────────────────────────
let scrollY = 0;

window.addEventListener('scroll', () => {
  scrollY = window.scrollY;

  // Porcentaje total de scroll (0 = inicio, 1 = final)
  const maxScroll = document.body.scrollHeight - window.innerHeight;
  const progreso  = scrollY / maxScroll; // 0.0 a 1.0
});

// ── RESIZE: actualizar Three.js al cambiar la ventana ────────
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
integración dom

2. Three.js dentro de un <div>

En la semana 08 el canvas ocupaba toda la pantalla. Esta semana lo integramos como cualquier otro elemento HTML: dentro de un <div> con dimensiones definidas, conviviendo con texto y otros elementos.

La diferencia clave: en lugar de usar window.innerWidth para el tamaño del renderer, usamos el ancho y alto del contenedor (contenedor.clientWidth). El aspect ratio de la cámara también cambia en consecuencia.

// ── THREE.JS EN UN DIV ESPECÍFICO ────────────────────────────
const contenedor = document.getElementById('mi-contenedor-3d');

// Obtener dimensiones del contenedor (no de la ventana)
const W = contenedor.clientWidth;
const H = contenedor.clientHeight;

const scene  = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, W / H, 0.1, 100);
camera.position.set(0, 0, 4);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(W, H);        // tamaño del contenedor, NO de window
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

// Añadir el canvas generado al div contenedor
contenedor.appendChild(renderer.domElement); // ← key line

// CSS necesario en el div contenedor:
// #mi-contenedor-3d { width: 100%; height: 400px; position: relative; }

Demo en vivo — escena dentro de un div

canvas dentro de un div (no fullscreen)
🖱 Arrastra para orbitar · Scroll para zoom
canvas fondo

3. Canvas como fondo de una sección

Un patrón muy usado en sitios modernos es poner el canvas 3D como fondo detrás del contenido HTML. Así el texto y los botones flotan sobre la escena 3D.

/* CSS: canvas fijo detrás de todo el contenido */
canvas {
  position: fixed;   /* fijo en el viewport */
  top: 0;  left: 0;
  width: 100%;  height: 100%;
  z-index: -1;  /* detrás de todo el HTML */
  pointer-events: none; /* los clicks pasan al HTML */
}

Demo en vivo — texto HTML sobre escena 3D

Contenido HTML
sobre escena 3D

Este texto está en el DOM normal. El canvas está en position: absolute detrás.

↕ MUEVE EL CURSOR SOBRE ESTA ZONA

pointer-events: none en el canvas es esencial cuando el canvas está como fondo: permite que los clicks "atraviesen" el canvas y lleguen al HTML que está debajo en el z-index. Sin esto, los links y botones dejarían de funcionar.

scroll

4. Scroll → animación 3D

El evento scroll nos da la posición vertical de la página. Podemos mapear ese valor para mover la cámara, rotar objetos o cambiar cualquier propiedad de la escena 3D.

// ── PATRÓN: scroll controla la cámara ────────────────────────
let scrollY = 0;
let currentSection = 0;

window.addEventListener('scroll', () => {
  scrollY = window.scrollY;

  // Calcular sección actual (asumiendo secciones de 100vh)
  currentSection = Math.round(scrollY / window.innerHeight);
});

// En el loop de animación — movimiento suave con lerp
let cameraY = 0; // posición actual suavizada

function animate() {
  requestAnimationFrame(animate);

  // lerp = interpolación lineal. Fórmula: actual + (destino - actual) * velocidad
  // Hace que la cámara se "arrastre" suavemente hacia la posición destino.
  cameraY += (-scrollY * 0.002 - cameraY) * 0.08;
  camera.position.y = cameraY;

  // Alternativamente, rotar un objeto proporcional al scroll
  mesh.rotation.y = scrollY * 0.001;

  renderer.render(scene, camera);
}

Demo en vivo — scroll controla la escena

window.scrollY → rotación y posición 3D
↕ Desplázate por la página para animar la escena scrollY: 0
mousemove

5. mousemove → escena 3D

El evento mousemove nos da las coordenadas del cursor. Al normalizar esas coordenadas (convertir de píxeles a valores entre -1 y +1), podemos usarlas para inclinar la cámara o mover objetos en respuesta al movimiento del mouse.

// ── COORDENADAS NORMALIZADAS (NDC) ───────────────────────────
// Three.js usa el sistema NDC: centro = (0,0), esquinas = (±1, ±1)
// Necesitamos convertir píxeles del DOM a ese sistema.

const mouse = new THREE.Vector2(); // x, y normalizados

window.addEventListener('mousemove', (e) => {
  mouse.x = (e.clientX / window.innerWidth)  * 2 - 1;
  mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
  // El eje Y se invierte porque DOM: Y crece hacia abajo
  //                              Three.js: Y crece hacia arriba
});

// En el loop: mover la cámara o un objeto con lerp para suavidad
let targetX = 0, targetY = 0;

function animate() {
  requestAnimationFrame(animate);

  // La cámara "sigue" al mouse con inercia
  targetX += (mouse.x * 0.5 - targetX) * 0.05; // lerp X
  targetY += (mouse.y * 0.3 - targetY) * 0.05; // lerp Y

  camera.position.x = targetX;
  camera.position.y = targetY;
  camera.lookAt(0, 0, 0); // siempre mirando al centro

  renderer.render(scene, camera);
}

Demo en vivo — cursor mueve la cámara

mousemove → cámara con lerp · coordenadas normalizadas
Mueve el cursor sobre la escena · La cámara te sigue con inercia NDC: (0.00, 0.00)
raycasting

6. Raycasting — detectar clicks en objetos 3D

Raycasting es la técnica para detectar qué objeto 3D está bajo el cursor del mouse. Three.js proyecta un rayo invisible desde la cámara en la dirección del cursor y detecta con qué objetos intersecta.

// ── RAYCASTER: detectar clicks sobre objetos 3D ───────────────
const raycaster = new THREE.Raycaster();
const mouse     = new THREE.Vector2();

const objetos   = [mesh1, mesh2, mesh3]; // los objetos "clicables"

window.addEventListener('click', (e) => {
  // 1. Normalizar coordenadas del click
  mouse.x = (e.clientX / window.innerWidth)  * 2 - 1;
  mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;

  // 2. Lanzar el rayo desde la cámara hacia el punto del click
  raycaster.setFromCamera(mouse, camera);

  // 3. Calcular intersecciones con los objetos
  const intersecciones = raycaster.intersectObjects(objetos);

  if (intersecciones.length > 0) {
    const objetoCliqueado = intersecciones[0].object; // el más cercano
    const punto = intersecciones[0].point;           // punto 3D exacto

    console.log('Clicaste:', objetoCliqueado.name);
    objetoCliqueado.material.color.setHex(0xff6b35); // cambiar color
  }
});

// Para hover (mousemove en lugar de click):
window.addEventListener('mousemove', (e) => {
  mouse.x = (e.clientX / window.innerWidth)  * 2 - 1;
  mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  const hits = raycaster.intersectObjects(objetos);
  document.body.style.cursor = hits.length > 0 ? 'pointer' : 'default';
});

Demo en vivo — click en objetos 3D

THREE.Raycaster · click detecta el objeto exacto
Haz click sobre cualquier cubo
🖱 Click sobre un cubo para seleccionarlo · Hover cambia el cursor
entregable

7. Proyecto Aplicado

🌐 Interfaz web con escena 3D integrada

Construye una página web completa donde una escena Three.js sea parte de la interfaz (no toda la pantalla), y que reaccione a al menos dos eventos del usuario: scroll, mousemove, click o teclas.

Requisitos mínimos

  • Canvas Three.js dentro de un <div> con dimensiones definidas (no pantalla completa).
  • Contenido HTML conviviendo con la escena 3D: al menos un título, un párrafo y un botón.
  • Evento scroll: la escena reacciona al desplazamiento (rotación, movimiento o cambio de color).
  • Evento mousemove: la cámara o un objeto sigue al cursor con suavizado (lerp).
  • Evento click con Raycaster: al hacer click sobre un objeto 3D ocurre algo visible.
  • Evento resize: la escena se adapta al cambiar el tamaño de ventana.
  • Al menos un efecto de lerp para movimiento fluido.

Criterios de evaluación

🧩 Integración DOM 25 pts

  • Canvas en un contenedor, no fullscreen
  • HTML y Three.js conviven correctamente
  • CSS y layout bien estructurado

🎛 Eventos JS 25 pts

  • Mínimo 2 eventos diferentes
  • Uso correcto de addEventListener
  • Coordenadas normalizadas para mouse

🌐 Escena 3D 25 pts

  • Escena completa: luces, materiales, loop
  • Resize listener implementado
  • Lerp para movimiento suave

✨ Calidad 25 pts

  • Código limpio y comentado
  • Raycasting funcional (bonus)
  • Experiencia visual coherente

💡 Tip: Empieza por la estructura HTML y CSS, luego añade Three.js. Es más fácil integrar una escena 3D en una interfaz ya diseñada que diseñar la interfaz alrededor de una escena 3D ya existente. El lerp es tu mejor amigo para todo movimiento.