Eventos y Event Listeners
¡Ahora viene la magia! Los eventos son las acciones que puede realizar el usuario (o el navegador) como hacer click, escribir, mover el mouse, etc. Los Event Listeners son las "orejas" que ponemos en JavaScript para escuchar estos eventos y reaccionar a ellos.
¿Qué son los eventos?
Un evento es algo que sucede en la página web:
- 👆 El usuario hace click en un botón
- ⌨️ El usuario escribe en un input
- 🖱️ El usuario mueve el mouse sobre un elemento
- 📋 El usuario envía un formulario
- 🔄 La página termina de cargar
Los eventos son la base de la interactividad en las páginas web.
Event Listeners con addEventListener()
addEventListener() es el método moderno y recomendado para escuchar eventos:
elemento.addEventListener('tipoDeEvento', funcionQueSeEjecuta)
Ejemplo básico
<button id="mi-boton">¡Haz click!</button>
const boton = document.querySelector('#mi-boton')
// Escuchar el evento 'click'
boton.addEventListener('click', function() {
console.log('¡Botón clickeado!')
})
// Con arrow function (más común)
boton.addEventListener('click', () => {
console.log('¡Botón clickeado!')
})
Infografía
Qué pasa cuando escuchas un evento
app.js
1
boton.addEventListener('click', (evento) => {2
console.log('¡Botón clickeado!')3
})La página
¡Haz click!Escuchando «click»
Objeto Event
type: 'click'
target: <button>
clientX: 124
clientY: 48
Consola▍
›¡Botón clickeado!
›¡Botón clickeado!
5.Cada nuevo clic vuelve a ejecutar la misma función, con un objeto Event nuevo.
Tipos de eventos comunes
Eventos de Mouse
click - Click del mouse
const boton = document.querySelector('#boton')
boton.addEventListener('click', () => {
console.log('Click en el botón')
})
dblclick - Doble click
const imagen = document.querySelector('#imagen')
imagen.addEventListener('dblclick', () => {
console.log('Doble click en la imagen')
})
mouseenter y mouseleave - Entrar y salir con el mouse
const caja = document.querySelector('#caja')
caja.addEventListener('mouseenter', () => {
caja.style.backgroundColor = 'lightblue'
console.log('Mouse entró en la caja')
})
caja.addEventListener('mouseleave', () => {
caja.style.backgroundColor = 'white'
console.log('Mouse salió de la caja')
})
Eventos de Teclado
keydown - Cuando se presiona una tecla
const input = document.querySelector('#mi-input')
input.addEventListener('keydown', (evento) => {
console.log('Tecla presionada:', evento.key)
if (evento.key === 'Enter') {
console.log('Se presionó Enter')
}
})
keyup - Cuando se suelta una tecla
input.addEventListener('keyup', (evento) => {
console.log('Tecla soltada:', evento.key)
console.log('Valor actual del input:', input.value)
})
Eventos de Formulario
submit - Envío de formulario
const formulario = document.querySelector('#mi-formulario')
formulario.addEventListener('submit', (evento) => {
evento.preventDefault() // Evita que se recargue la página
console.log('Formulario enviado')
// Aquí puedes procesar los datos del formulario
})
input - Cambio en input (en tiempo real)
const input = document.querySelector('#busqueda')
input.addEventListener('input', () => {
console.log('Usuario escribiendo:', input.value)
// Ideal para búsquedas en tiempo real
})
change - Cambio en input (cuando pierde el foco)
const select = document.querySelector('#pais')
select.addEventListener('change', () => {
console.log('País seleccionado:', select.value)
})
Eventos de Ventana
load - Cuando la página termina de cargar
window.addEventListener('load', () => {
console.log('Página completamente cargada')
})
resize - Cuando se redimensiona la ventana
window.addEventListener('resize', () => {
console.log('Ventana redimensionada')
console.log('Nuevo ancho:', window.innerWidth)
console.log('Nueva altura:', window.innerHeight)
})
El objeto Event
Cuando ocurre un evento, JavaScript crea un objeto Event con información útil:
boton.addEventListener('click', (evento) => {
console.log('Objeto evento:', evento)
console.log('Tipo de evento:', evento.type) // 'click'
console.log('Elemento que disparó:', evento.target) // El botón
console.log('Posición X:', evento.clientX) // Posición del click
console.log('Posición Y:', evento.clientY)
})
Propiedades útiles del objeto Event
input.addEventListener('keydown', (evento) => {
console.log('Tecla:', evento.key) // Tecla presionada
console.log('Código:', evento.code) // Código físico de la tecla
console.log('Ctrl presionado:', evento.ctrlKey) // true/false
console.log('Shift presionado:', evento.shiftKey) // true/false
console.log('Alt presionado:', evento.altKey) // true/false
})
preventDefault() - Prevenir comportamiento por defecto
const enlace = document.querySelector('#mi-enlace')
enlace.addEventListener('click', (evento) => {
evento.preventDefault() // Evita que el enlace navegue
console.log('Click en enlace, pero no navega')
})
const formulario = document.querySelector('#formulario')
formulario.addEventListener('submit', (evento) => {
evento.preventDefault() // Evita que se recargue la página
console.log('Formulario procesado sin recargar')
})
Ejemplos prácticos
Ejemplo 1: Contador interactivo completo
<div id="contador-app">
<h2>Contador: <span id="numero">0</span></h2>
<div class="botones">
<button id="decrementar">-1</button>
<button id="reset">Reset</button>
<button id="incrementar">+1</button>
</div>
<div class="controles">
<input type="number" id="paso" value="1" min="1">
<label for="paso">Paso</label>
</div>
</div>
// Estado
let contador = 0
// Seleccionar elementos
const numeroElement = document.querySelector('#numero')
const btnIncrementar = document.querySelector('#incrementar')
const btnDecrementar = document.querySelector('#decrementar')
const btnReset = document.querySelector('#reset')
const inputPaso = document.querySelector('#paso')
// Función para actualizar la UI
function actualizarContador() {
numeroElement.textContent = contador
// Cambiar color según el valor
numeroElement.className = '' // Limpiar clases
if (contador > 0) {
numeroElement.classList.add('positivo')
} else if (contador < 0) {
numeroElement.classList.add('negativo')
}
}
// Event listeners
btnIncrementar.addEventListener('click', () => {
const paso = parseInt(inputPaso.value) || 1
contador += paso
actualizarContador()
})
btnDecrementar.addEventListener('click', () => {
const paso = parseInt(inputPaso.value) || 1
contador -= paso
actualizarContador()
})
btnReset.addEventListener('click', () => {
contador = 0
actualizarContador()
})
// Atajos de teclado
document.addEventListener('keydown', (evento) => {
if (evento.key === 'ArrowUp') {
evento.preventDefault()
contador++
actualizarContador()
} else if (evento.key === 'ArrowDown') {
evento.preventDefault()
contador--
actualizarContador()
} else if (evento.key === 'r' || evento.key === 'R') {
contador = 0
actualizarContador()
}
})
Ejemplo 2: Validación de formulario en tiempo real
<form id="formulario-registro">
<div class="campo">
<label for="email">Email:</label>
<input type="email" id="email" required>
<span class="error" id="error-email"></span>
</div>
<div class="campo">
<label for="password">Contraseña:</label>
<input type="password" id="password" required>
<span class="error" id="error-password"></span>
</div>
<div class="campo">
<label for="confirmar-password">Confirmar Contraseña:</label>
<input type="password" id="confirmar-password" required>
<span class="error" id="error-confirmar"></span>
</div>
<button type="submit" id="btn-enviar" disabled>Registrarse</button>
</form>
// Seleccionar elementos
const formulario = document.querySelector('#formulario-registro')
const emailInput = document.querySelector('#email')
const passwordInput = document.querySelector('#password')
const confirmarInput = document.querySelector('#confirmar-password')
const btnEnviar = document.querySelector('#btn-enviar')
// Elementos de error
const errorEmail = document.querySelector('#error-email')
const errorPassword = document.querySelector('#error-password')
const errorConfirmar = document.querySelector('#error-confirmar')
// Estado de validación
let validacion = {
email: false,
password: false,
confirmar: false
}
// Función para actualizar el botón de envío
function actualizarBotonEnvio() {
const todoValido = Object.values(validacion).every(valido => valido)
btnEnviar.disabled = !todoValido
}
// Validar email
emailInput.addEventListener('input', () => {
const email = emailInput.value
const esValido = email.includes('@') && email.includes('.')
if (email.length === 0) {
errorEmail.textContent = ''
validacion.email = false
} else if (esValido) {
errorEmail.textContent = ''
errorEmail.style.color = 'green'
errorEmail.textContent = '✓ Email válido'
validacion.email = true
} else {
errorEmail.style.color = 'red'
errorEmail.textContent = '✗ Email no válido'
validacion.email = false
}
actualizarBotonEnvio()
})
// Validar contraseña
passwordInput.addEventListener('input', () => {
const password = passwordInput.value
const esValida = password.length >= 6
if (password.length === 0) {
errorPassword.textContent = ''
validacion.password = false
} else if (esValida) {
errorPassword.style.color = 'green'
errorPassword.textContent = '✓ Contraseña válida'
validacion.password = true
} else {
errorPassword.style.color = 'red'
errorPassword.textContent = '✗ Mínimo 6 caracteres'
validacion.password = false
}
// Re-validar confirmación si ya tiene contenido
if (confirmarInput.value.length > 0) {
confirmarInput.dispatchEvent(new Event('input'))
}
actualizarBotonEnvio()
})
// Validar confirmación de contraseña
confirmarInput.addEventListener('input', () => {
const confirmar = confirmarInput.value
const password = passwordInput.value
const coinciden = confirmar === password && confirmar.length > 0
if (confirmar.length === 0) {
errorConfirmar.textContent = ''
validacion.confirmar = false
} else if (coinciden) {
errorConfirmar.style.color = 'green'
errorConfirmar.textContent = '✓ Contraseñas coinciden'
validacion.confirmar = true
} else {
errorConfirmar.style.color = 'red'
errorConfirmar.textContent = '✗ Las contraseñas no coinciden'
validacion.confirmar = false
}
actualizarBotonEnvio()
})
// Manejar envío del formulario
formulario.addEventListener('submit', (evento) => {
evento.preventDefault()
console.log('Formulario válido enviado:')
console.log('Email:', emailInput.value)
console.log('Password:', passwordInput.value)
// Aquí harías la petición al servidor
alert('¡Registro exitoso!')
})
Ejemplo 3: Galería de imágenes interactiva
<div id="galeria">
<div class="imagen-principal">
<img id="imagen-grande" src="imagen1.jpg" alt="Imagen principal">
</div>
<div class="miniaturas">
<img src="imagen1.jpg" alt="Imagen 1" class="miniatura activa" data-src="imagen1.jpg">
<img src="imagen2.jpg" alt="Imagen 2" class="miniatura" data-src="imagen2.jpg">
<img src="imagen3.jpg" alt="Imagen 3" class="miniatura" data-src="imagen3.jpg">
<img src="imagen4.jpg" alt="Imagen 4" class="miniatura" data-src="imagen4.jpg">
</div>
<div class="controles">
<button id="anterior">← Anterior</button>
<button id="siguiente">Siguiente →</button>
</div>
</div>
// Seleccionar elementos
const imagenGrande = document.querySelector('#imagen-grande')
const miniaturas = document.querySelectorAll('.miniatura')
const btnAnterior = document.querySelector('#anterior')
const btnSiguiente = document.querySelector('#siguiente')
let imagenActual = 0
// Función para cambiar imagen
function cambiarImagen(indice) {
// Actualizar imagen grande
imagenGrande.src = miniaturas[indice].dataset.src
imagenGrande.alt = miniaturas[indice].alt
// Actualizar clases activas
miniaturas.forEach(mini => mini.classList.remove('activa'))
miniaturas[indice].classList.add('activa')
// Actualizar índice actual
imagenActual = indice
}
// Event listeners para miniaturas
miniaturas.forEach((miniatura, indice) => {
miniatura.addEventListener('click', () => {
cambiarImagen(indice)
})
// Efecto hover
miniatura.addEventListener('mouseenter', () => {
miniatura.style.opacity = '0.7'
})
miniatura.addEventListener('mouseleave', () => {
miniatura.style.opacity = '1'
})
})
// Botones de navegación
btnAnterior.addEventListener('click', () => {
const nuevoIndice = imagenActual === 0 ? miniaturas.length - 1 : imagenActual - 1
cambiarImagen(nuevoIndice)
})
btnSiguiente.addEventListener('click', () => {
const nuevoIndice = imagenActual === miniaturas.length - 1 ? 0 : imagenActual + 1
cambiarImagen(nuevoIndice)
})
// Navegación con teclado
document.addEventListener('keydown', (evento) => {
if (evento.key === 'ArrowLeft') {
btnAnterior.click()
} else if (evento.key === 'ArrowRight') {
btnSiguiente.click()
}
})
Event Delegation (Delegación de Eventos)
Cuando tienes muchos elementos similares, en lugar de añadir un event listener a cada uno, puedes usar delegación:
<ul id="lista-tareas">
<li>Tarea 1 <button class="eliminar">X</button></li>
<li>Tarea 2 <button class="eliminar">X</button></li>
<li>Tarea 3 <button class="eliminar">X</button></li>
<!-- Se pueden añadir más dinámicamente -->
</ul>
const lista = document.querySelector('#lista-tareas')
// Un solo event listener en el contenedor padre
lista.addEventListener('click', (evento) => {
// Verificar si se clickeó un botón de eliminar
if (evento.target.classList.contains('eliminar')) {
const tarea = evento.target.parentElement
tarea.remove()
}
})
// Añadir nuevas tareas dinámicamente
function agregarTarea(texto) {
const li = document.createElement('li')
li.innerHTML = `${texto} <button class="eliminar">X</button>`
lista.appendChild(li)
// ¡El event listener ya funciona automáticamente!
}
Infografía
Delegación de eventos: un listener para toda la lista
app.js
1
lista.addEventListener('click', (evento) => {2
if (evento.target.classList.contains('eliminar')) {3
evento.target.parentElement.remove()4
}5
})ul#lista-tareaslistener de click
liTarea 1X
liTarea 2X
Tarea 2 eliminada
liTarea 3X
+ aquí irán las tareas nuevas
liTarea 4 (nueva)X
Recorrido del evento
ul#lista-tareastiene el listener ✓
↑ burbujealiparentElement
↑ burbujeabutton.eliminarevento.target
7.Las tareas que añadas después también funcionan sin hacer nada más: el listener está en la
<ul>.Remover Event Listeners
function manejarClick() {
console.log('Click manejado')
}
const boton = document.querySelector('#boton')
// Añadir
boton.addEventListener('click', manejarClick)
// Remover (debe ser la misma función)
boton.removeEventListener('click', manejarClick)
Buenas prácticas
✅ Hacer
// Usar addEventListener (método moderno)
boton.addEventListener('click', manejarClick)
// Usar arrow functions para funciones simples
boton.addEventListener('click', () => {
console.log('Click simple')
})
// Usar preventDefault cuando sea necesario
formulario.addEventListener('submit', (evento) => {
evento.preventDefault()
// Procesar formulario
})
// Delegación para elementos dinámicos
contenedor.addEventListener('click', (evento) => {
if (evento.target.matches('.boton-dinamico')) {
// Manejar click
}
})
❌ Evitar
// No usar onclick (método antiguo)
boton.onclick = manejarClick // ❌ Solo permite un listener
// No crear funciones anónimas si necesitas removerlas
boton.addEventListener('click', function() {
console.log('No se puede remover')
}) // ❌ No se puede remover después
// No añadir muchos listeners iguales
elementos.forEach(elemento => {
elemento.addEventListener('click', mismaFuncion) // ❌ Ineficiente
})
Resumen
- Eventos: Acciones que ocurren en la página (click, keydown, submit, etc.)
addEventListener(): Método moderno para escuchar eventos- Objeto Event: Contiene información útil sobre el evento
preventDefault(): Previene el comportamiento por defecto- Event Delegation: Técnica eficiente para elementos dinámicos
- Los eventos son la clave para crear páginas interactivas y dinámicas
En la próxima lección pondremos todo junto en un proyecto práctico completo.