DOM y Eventos
Aprende a interactuar con páginas web mediante JavaScript, manipulando el DOM y respondiendo a eventos del usuario.
Cristian Escalante
Última actualización: 21 de abril de 2025
¿Qué es el DOM?
El DOM (Document Object Model) es una representación estructurada del documento HTML como un árbol de objetos que puede ser manipulado con JavaScript. Cada elemento HTML se convierte en un nodo del árbol, permitiendo acceder y modificar su contenido, estructura y estilo.
Estructura del DOM
Document
└── html
├── head
│ ├── title
│ ├── meta
│ └── link
└── body
├── header
│ └── nav
├── main
│ ├── section
│ │ └── article
│ └── aside
└── footer
Tipos de nodos
Los principales tipos de nodos en el DOM son:
- Document: El nodo raíz que representa el documento HTML completo
- Element: Nodos que representan elementos HTML (div, p, h1, etc.)
- Text: Nodos que contienen texto dentro de los elementos
- Attribute: Nodos que representan atributos de los elementos
- Comment: Nodos que representan comentarios HTML
Selección de elementos
JavaScript proporciona varios métodos para seleccionar elementos del DOM:
Por ID
// Selecciona un elemento con id="miElemento"
const elemento = document.getElementById('miElemento');
Por clase
// Selecciona todos los elementos con class="miClase"
const elementos = document.getElementsByClassName('miClase');
// Devuelve una HTMLCollection (similar a un array)
Por etiqueta
// Selecciona todos los elementos <p>
const parrafos = document.getElementsByTagName('p');
Por selectores CSS (métodos modernos)
// Selecciona el primer elemento que coincida con el selector
const elemento = document.querySelector('.miClase');
// Selecciona todos los elementos que coincidan con el selector
const elementos = document.querySelectorAll('p.destacado');
// Devuelve una NodeList
Diferencias entre NodeList y HTMLCollection
- HTMLCollection: Colección "viva" que se actualiza automáticamente cuando el DOM cambia
- NodeList: Colección estática (excepto algunas como
childNodes
) - Conversión a Array: Para usar métodos de array como
map
ofilter
// Convertir a array
const elementosArray = Array.from(document.querySelectorAll('.miClase'));
// o
const elementosArray = [...document.querySelectorAll('.miClase')];
Modificación del contenido y atributos
Contenido de texto
// Obtener o establecer el contenido de texto
const titulo = document.querySelector('h1');
console.log(titulo.textContent); // Obtener texto
titulo.textContent = 'Nuevo título'; // Establecer texto
Contenido HTML
// Obtener o establecer el HTML interno
const contenedor = document.querySelector('.contenedor');
console.log(contenedor.innerHTML); // Obtener HTML
contenedor.innerHTML = '<p>Nuevo <strong>contenido</strong></p>'; // Establecer HTML
Nota de seguridad: Ten cuidado al usar innerHTML
con contenido generado por usuarios, ya que puede crear vulnerabilidades XSS (Cross-Site Scripting).
Manipulación de atributos
const enlace = document.querySelector('a');
// Obtener atributos
console.log(enlace.getAttribute('href'));
console.log(enlace.href); // Acceso directo para atributos comunes
// Establecer atributos
enlace.setAttribute('href', 'https://ejemplo.com');
enlace.href = 'https://ejemplo.com'; // Acceso directo
// Comprobar si existe un atributo
console.log(enlace.hasAttribute('target'));
// Eliminar atributos
enlace.removeAttribute('target');
Manipulación de clases CSS
const elemento = document.querySelector('.miElemento');
// Añadir clases
elemento.classList.add('destacado');
// Eliminar clases
elemento.classList.remove('oculto');
// Alternar clases (añadir si no existe, eliminar si existe)
elemento.classList.toggle('activo');
// Comprobar si tiene una clase
if (elemento.classList.contains('destacado')) {
console.log('El elemento tiene la clase destacado');
}
// Reemplazar una clase
elemento.classList.replace('vieja', 'nueva');
Manipulación de estilos
const elemento = document.querySelector('.miElemento');
// Establecer estilos directamente
elemento.style.color = 'red';
elemento.style.backgroundColor = '#f0f0f0';
elemento.style.fontSize = '16px';
// Obtener estilos computados (después de aplicar CSS)
const estilos = window.getComputedStyle(elemento);
console.log(estilos.color);
console.log(estilos.fontSize);
Creación y eliminación de elementos
Crear elementos
// Crear un nuevo elemento
const nuevoParrafo = document.createElement('p');
nuevoParrafo.textContent = 'Este es un nuevo párrafo';
nuevoParrafo.classList.add('destacado');
// Crear un nodo de texto
const textoNodo = document.createTextNode('Texto simple');
Añadir elementos al DOM
const contenedor = document.querySelector('.contenedor');
const nuevoParrafo = document.createElement('p');
nuevoParrafo.textContent = 'Nuevo párrafo';
// Añadir al final del contenedor
contenedor.appendChild(nuevoParrafo);
// Añadir en una posición específica
const primerHijo = contenedor.firstChild;
contenedor.insertBefore(nuevoParrafo, primerHijo);
// Métodos modernos
contenedor.append(nuevoParrafo); // Al final (permite múltiples nodos y texto)
contenedor.prepend(nuevoParrafo); // Al principio
contenedor.before(nuevoParrafo); // Antes del elemento
contenedor.after(nuevoParrafo); // Después del elemento
Clonar elementos
const original = document.querySelector('.original');
// Clonar sin los descendientes
const clon = original.cloneNode(false);
// Clonar con todos los descendientes
const clonCompleto = original.cloneNode(true);
Eliminar elementos
const elemento = document.querySelector('.eliminar');
// Método antiguo
elemento.parentNode.removeChild(elemento);
// Método moderno
elemento.remove();
Reemplazar elementos
const viejo = document.querySelector('.viejo');
const nuevo = document.createElement('div');
nuevo.textContent = 'Elemento nuevo';
// Método antiguo
viejo.parentNode.replaceChild(nuevo, viejo);
// Método moderno
viejo.replaceWith(nuevo);
Navegación por el DOM
Relaciones padre-hijo
const padre = document.querySelector('.padre');
// Hijos
const hijos = padre.children; // HTMLCollection de elementos hijos
const primerHijo = padre.firstElementChild;
const ultimoHijo = padre.lastElementChild;
const todosLosNodos = padre.childNodes; // NodeList (incluye nodos de texto)
// Padre
const elementoPadre = hijo.parentElement;
const nodoPadre = hijo.parentNode;
Relaciones entre hermanos
const elemento = document.querySelector('.medio');
// Hermanos
const siguiente = elemento.nextElementSibling;
const anterior = elemento.previousElementSibling;
// Incluye nodos de texto
const siguienteNodo = elemento.nextSibling;
const anteriorNodo = elemento.previousSibling;
Recorrer el DOM
function recorrerDOM(elemento, callback) {
callback(elemento);
// Recorrer todos los hijos
const hijos = elemento.children;
for (let i = 0; i < hijos.length; i++) {
recorrerDOM(hijos[i], callback);
}
}
// Ejemplo de uso
recorrerDOM(document.body, (elemento) => {
console.log(elemento.tagName);
});
Introducción a los eventos
Los eventos son acciones o sucesos que ocurren en el navegador, como clics, pulsaciones de teclas o carga de la página.
Tipos comunes de eventos
- Mouse:
click
,dblclick
,mousedown
,mouseup
,mousemove
,mouseover
,mouseout
- Teclado:
keydown
,keyup
,keypress
- Formulario:
submit
,reset
,change
,input
,focus
,blur
- Documento:
DOMContentLoaded
,load
,unload
,resize
,scroll
- Arrastrar:
dragstart
,drag
,dragend
,drop
- Táctil:
touchstart
,touchmove
,touchend
Asignar manejadores de eventos
Método addEventListener
const boton = document.querySelector('#miBoton');
boton.addEventListener('click', function(evento) {
console.log('Botón clickeado');
console.log(evento); // Objeto del evento
});
// Con función flecha
boton.addEventListener('click', (e) => {
console.log(`Clickeado en: ${e.clientX}, ${e.clientY}`);
});
// Con función nombrada
function manejadorClick(e) {
console.log('Click detectado');
}
boton.addEventListener('click', manejadorClick);
// Eliminar un manejador de eventos
boton.removeEventListener('click', manejadorClick);
Propiedades de eventos
// Método antiguo (no recomendado para código moderno)
boton.onclick = function() {
console.log('Botón clickeado');
};
// Sobrescribe cualquier manejador anterior
boton.onclick = otraFuncion; // El manejador anterior se pierde
Eventos en línea (no recomendado)
<button onclick="console.log('Clickeado')">Haz clic</button>
El objeto Event
Cada manejador de eventos recibe un objeto Event
con información sobre el evento:
boton.addEventListener('click', function(evento) {
// Información general
console.log(evento.type); // "click"
console.log(evento.target); // Elemento que disparó el evento
console.log(evento.currentTarget); // Elemento al que está adjunto el manejador
// Coordenadas (para eventos de mouse)
console.log(evento.clientX, evento.clientY); // Relativas a la ventana
console.log(evento.pageX, evento.pageY); // Relativas al documento
// Teclas (para eventos de teclado)
console.log(evento.key); // Tecla presionada
console.log(evento.keyCode); // Código de la tecla (obsoleto)
console.log(evento.ctrlKey, evento.shiftKey); // Teclas modificadoras
// Prevenir comportamiento predeterminado
evento.preventDefault();
// Detener propagación
evento.stopPropagation();
});
Event bubbling y capturing
Cuando ocurre un evento en un elemento, este se propaga a través del DOM:
Fases de propagación
- Fase de captura: El evento desciende desde el documento hasta el elemento objetivo
- Fase de objetivo: El evento llega al elemento donde ocurrió
- Fase de burbujeo: El evento asciende desde el elemento objetivo hasta el documento
// El tercer parámetro controla la fase:
// false (predeterminado): fase de burbujeo
// true: fase de captura
elemento.addEventListener('click', manejador, false); // Burbujeo
elemento.addEventListener('click', manejador, true); // Captura
// Ejemplo que muestra la propagación
document.body.addEventListener('click', () => {
console.log('Click en body (burbujeo)');
});
document.querySelector('div').addEventListener('click', (e) => {
console.log('Click en div (burbujeo)');
// Detener la propagación
// e.stopPropagation();
});
document.querySelector('button').addEventListener('click', () => {
console.log('Click en botón (burbujeo)');
});
Detener la propagación
elemento.addEventListener('click', (e) => {
e.stopPropagation(); // Detiene la propagación
e.stopImmediatePropagation(); // Detiene la propagación y otros manejadores en el mismo elemento
});
Delegación de eventos
La delegación de eventos es una técnica que aprovecha el burbujeo para manejar eventos en múltiples elementos con un solo manejador:
// En lugar de asignar un manejador a cada botón
document.querySelector('.contenedor').addEventListener('click', (e) => {
// Comprobar si el clic fue en un botón
if (e.target.matches('button') || e.target.closest('button')) {
console.log('Botón clickeado:', e.target);
// Acceder a atributos de datos
const id = e.target.dataset.id;
if (id) {
console.log(`ID del botón: ${id}`);
}
}
});
Ventajas de la delegación de eventos
- Rendimiento mejorado: Menos manejadores de eventos
- Manejo dinámico: Funciona con elementos añadidos después de cargar la página
- Menos código: Simplifica la gestión de eventos en listas o tablas grandes
Ejemplo práctico: Lista de tareas
const lista = document.querySelector('#tareas');
lista.addEventListener('click', (e) => {
const tarea = e.target.closest('.tarea');
if (!tarea) return; // No se hizo clic en una tarea
if (e.target.matches('.eliminar')) {
// Clic en el botón eliminar
tarea.remove();
} else if (e.target.matches('.completar')) {
// Clic en el botón completar
tarea.classList.toggle('completada');
} else {
// Clic en la tarea
console.log('Tarea seleccionada:', tarea.textContent);
}
});
Eventos personalizados
JavaScript permite crear y disparar eventos personalizados:
// Crear un evento personalizado
const eventoPersonalizado = new CustomEvent('miEvento', {
detail: {
mensaje: 'Hola desde evento personalizado',
timestamp: Date.now()
},
bubbles: true,
cancelable: true
});
// Escuchar el evento personalizado
document.addEventListener('miEvento', (e) => {
console.log('Evento personalizado recibido:', e.detail.mensaje);
});
// Disparar el evento
document.dispatchEvent(eventoPersonalizado);
Mejores prácticas
- Usar addEventListener en lugar de propiedades de eventos:
// Bien elemento.addEventListener('click', manejador); // Evitar elemento.onclick = manejador;
- Delegar eventos cuando sea posible:
// Bien contenedor.addEventListener('click', manejarClicsEnHijos); // Evitar (para muchos elementos) elementos.forEach(el => el.addEventListener('click', manejador));
- Eliminar manejadores cuando ya no sean necesarios:
// Guardar referencia a la función const manejador = (e) => { /* código */ }; elemento.addEventListener('click', manejador); // Eliminar cuando no sea necesario elemento.removeEventListener('click', manejador);
- Usar funciones nombradas para manejadores complejos:
// Bien function manejarEnvio(e) { // Lógica compleja } formulario.addEventListener('submit', manejarEnvio); // Evitar para lógica compleja formulario.addEventListener('submit', function(e) { // Mucho código... });
- Prevenir comportamientos predeterminados con cuidado:
formulario.addEventListener('submit', (e) => { // Prevenir solo cuando sea necesario if (!validarFormulario()) { e.preventDefault(); } });