Asignatura: Programación IV
Unidad III: Manipulación del DOM (Modelo de Objeto de Documento) y Eventos
Duración: 90 Minutos
Objetivo: Comprender y aplicar la manipulación del DOM y el manejo de eventos en JavaScript para crear aplicaciones web interactivas y dinámicas, optimizando la experiencia del usuario mediante buenas prácticas de programación.
El DOM (Document Object Model) es una interfaz de programación que representa la estructura de un documento HTML como un árbol de objetos. Cada elemento HTML se convierte en un nodo del árbol, permitiendo acceder y modificar su contenido, estructura y estilo de forma dinámica mediante JavaScript.
Conceptos clave:
Documento: La página HTML completa
Elementos: Etiquetas HTML (div, p, span, etc.)
Atributos: Propiedades de los elementos (class, id, src, etc.)
Texto: Contenido dentro de los elementos
JavaScript proporciona múltiples métodos para seleccionar elementos del DOM:
Métodos tradicionales:
// Por ID (retorna un solo elemento)
const elemento = document.getElementById('miId');
// Por clase (retorna una colección HTMLCollection)
const elementos = document.getElementsByClassName('miClase');
// Por etiqueta (retorna una colección HTMLCollection)
const parrafos = document.getElementsByTagName('p');
Métodos modernos (recomendados):
// querySelector: retorna el PRIMER elemento que coincida
const primerBoton = document.querySelector('.boton');
const titulo = document.querySelector('#titulo');
const inputEmail = document.querySelector('input[type="email"]');
// querySelectorAll: retorna TODOS los elementos que coincidan (NodeList)
const todosLosBotones = document.querySelectorAll('.boton');
const itemsLista = document.querySelectorAll('ul li');
Ejercicio 1: Crear un archivo HTML con la siguiente estructura y seleccionar los elementos indicados:
<!DOCTYPE html>
<html>
<head>
<title>Mi Página</title>
</head>
<body>
<header id="cabecera">
<h1 class="titulo-principal">Bienvenido</h1>
<nav>
<a href="#" class="link">Inicio</a>
<a href="#" class="link">Contacto</a>
</nav>
</header>
<main>
<section id="contenido">
<p class="texto">Primer párrafo</p>
<p class="texto destacado">Segundo párrafo</p>
</section>
</main>
</body>
</html>
Seleccionar:
El elemento con id "cabecera"
Todos los elementos con clase "link"
El primer párrafo con clase "texto"
Todos los párrafos dentro de "contenido"
// Crear un nuevo elemento
const nuevoDiv = document.createElement('div');
// Crear contenido de texto
const texto = document.createTextNode('Hola Mundo');
// Agregar texto al elemento
nuevoDiv.appendChild(texto);
// O de forma más directa
nuevoDiv.textContent = 'Hola Mundo';
nuevoDiv.innerHTML = '<strong>Hola Mundo</strong>';
// Modificar contenido
elemento.textContent = 'Nuevo texto';
elemento.innerHTML = '<em>Texto con formato</em>';
// Modificar estilos
elemento.style.color = 'red';
elemento.style.backgroundColor = 'blue';
elemento.style.fontSize = '20px';
const padre = document.getElementById('contenedor');
const hijo = document.createElement('p');
hijo.textContent = 'Nuevo párrafo';
// Agregar al final
padre.appendChild(hijo);
// Agregar antes de un elemento específico
const referencia = document.querySelector('.elemento-existente');
padre.insertBefore(hijo, referencia);
// Métodos modernos (más flexibles)
padre.append(hijo); // Al final
padre.prepend(hijo); // Al inicio
referencia.before(hijo); // Antes del elemento
referencia.after(hijo); // Después del elemento
// Método tradicional
padre.removeChild(hijo);
// Método moderno (desde el elemento mismo)
hijo.remove();
Ejercicio 2: Usando el HTML del Ejercicio 1, realizar las siguientes operaciones en la consola del navegador:
Crear un nuevo elemento <article> con id "articulo-nuevo"
Agregarle un título <h2> con el texto "Artículo Dinámico"
Agregarle un párrafo con el texto "Este fue creado con JavaScript"
Insertar el artículo completo dentro de la sección "contenido"
Cambiar el color del título principal a verde
const imagen = document.querySelector('img');
// Obtener atributo
const src = imagen.getAttribute('src');
const alt = imagen.getAttribute('alt');
// Establecer atributo
imagen.setAttribute('src', 'nueva-imagen.jpg');
imagen.setAttribute('alt', 'Descripción nueva');
// Verificar si existe un atributo
const tieneSrc = imagen.hasAttribute('src');
// Eliminar atributo
imagen.removeAttribute('alt');
// Acceso directo (propiedades)
imagen.src = 'otra-imagen.jpg';
imagen.id = 'miImagen';
const elemento = document.querySelector('.caja');
// Agregar clase
elemento.classList.add('activa');
elemento.classList.add('grande', 'roja'); // Múltiples clases
// Eliminar clase
elemento.classList.remove('oculto');
// Alternar clase (agrega si no existe, elimina si existe)
elemento.classList.toggle('visible');
// Verificar si tiene una clase
const tieneActiva = elemento.classList.contains('activa');
// Reemplazar clase
elemento.classList.replace('roja', 'azul');
Ejercicio 3: Crear un botón que al hacer clic:
Cambie el texto del botón de "Activar" a "Desactivar"
Agregue la clase "activo" al botón (definir en CSS: .activo { background: green; color: white; })
Cambie el atributo data-estado del botón a "activo"
Los eventos son acciones que ocurren en el navegador: clics del mouse, pulsaciones de teclas, carga de página, envío de formularios, etc. JavaScript permite "escuchar" estos eventos y ejecutar código en respuesta.
Método tradicional (inline - no recomendado):
<button onclick="saludar()">Clic aquí</button>
Método moderno (recomendado):
const boton = document.querySelector('#miBoton');
// Agregar event listener
boton.addEventListener('click', function() {
console.log('Botón clickeado');
});
// Con función nombrada (mejor práctica)
function saludar() {
alert('Hola!');
}
boton.addEventListener('click', saludar);
// Con arrow function
boton.addEventListener('click', () => {
console.log('Clic con arrow function');
});
Eventos de Mouse:
elemento.addEventListener('click', () => {}); // Clic
elemento.addEventListener('dblclick', () => {}); // Doble clic
elemento.addEventListener('mouseover', () => {}); // Mouse entra
elemento.addEventListener('mouseout', () => {}); // Mouse sale
elemento.addEventListener('mousemove', () => {}); // Mouse se mueve
Eventos de Teclado:
input.addEventListener('keydown', (e) => {}); // Tecla presionada
input.addEventListener('keyup', (e) => {}); // Tecla liberada
input.addEventListener('keypress', (e) => {}); // Tecla presionada (caracter)
Eventos de Formulario:
form.addEventListener('submit', (e) => {
e.preventDefault(); // Evitar envío por defecto
});
input.addEventListener('change', () => {}); // Cambio de valor
input.addEventListener('focus', () => {}); // Enfocar
input.addEventListener('blur', () => {}); // Perder foco
Eventos del Documento:
window.addEventListener('load', () => {}); // Página cargada
window.addEventListener('resize', () => {}); // Cambio de tamaño
window.addEventListener('scroll', () => {}); // Scroll
Cuando ocurre un evento, JavaScript crea un objeto Event con información sobre lo sucedido:
boton.addEventListener('click', (evento) => {
console.log(evento.type); // Tipo de evento: 'click'
console.log(evento.target); // Elemento que disparó el evento
console.log(evento.clientX); // Coordenada X del mouse
console.log(evento.clientY); // Coordenada Y del mouse
console.log(evento.timeStamp); // Timestamp del evento
evento.preventDefault(); // Prevenir comportamiento por defecto
evento.stopPropagation(); // Detener propagación
});
Ejercicio 4: Crear una página con:
Un input de texto
Un párrafo vacío
Al escribir en el input, mostrar en el párrafo: "Escribiste: [texto]" en tiempo real (usar evento input)
Al presionar Enter en el input, agregar el texto a una lista <ul> debajo
En lugar de agregar event listeners a cada elemento hijo, se agrega UN solo listener al elemento padre. Los eventos "burbujean" hacia arriba en el DOM, permitiendo capturarlos en el padre.
Ventajas:
Mejor rendimiento (menos listeners)
Funciona con elementos creados dinámicamente
Código más limpio y mantenible
<ul id="lista">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
Sin delegación (ineficiente):
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', () => {
console.log('Clic en item');
});
});
Con delegación (recomendado):
const lista = document.getElementById('lista');
lista.addEventListener('click', (evento) => {
// Verificar si el clic fue en un item
if (evento.target.classList.contains('item')) {
console.log('Clic en:', evento.target.textContent);
// O usando closest() para buscar el ancestro más cercano
const item = evento.target.closest('.item');
if (item) {
item.style.backgroundColor = 'yellow';
}
}
});
Ejercicio 5: Crear una lista de tareas donde:
Al hacer clic en cualquier tarea, se marque como completada (agregar clase "completada")
Al hacer doble clic, se elimine la tarea
Usar delegación de eventos en el contenedor <ul>
Agregar un botón que cree nuevas tareas dinámicamente (deben funcionar con los eventos)
Problema: Modificaciones frecuentes al DOM causan reflows y repaints, afectando el rendimiento.
Solución 1: Usar DocumentFragment
// MALO: Causa múltiples reflows
const contenedor = document.getElementById('contenedor');
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
contenedor.appendChild(div); // Reflow en cada iteración
}
// BUENO: Un solo reflow
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div); // No causa reflow
}
contenedor.appendChild(fragment); // Un solo reflow al final
Solución 2: Manipular fuera del DOM
// Crear estructura completa antes de insertar
const contenedor = document.createElement('div');
contenedor.innerHTML = `
<h1>Título</h1>
<p>Párrafo 1</p>
<p>Párrafo 2</p>
`;
document.body.appendChild(contenedor);
Técnica 1: Cachear referencias al DOM
// MALO: Busca en el DOM en cada iteración
for (let i = 0; i < 100; i++) {
document.getElementById('contenedor').appendChild(
document.createElement('div')
);
}
// BUENO: Cachear la referencia
const contenedor = document.getElementById('contenedor');
for (let i = 0; i < 100; i++) {
contenedor.appendChild(document.createElement('div'));
}
Técnica 2: Usar innerHTML para contenido estático
// Para grandes cantidades de contenido estático
const datos = ['Item 1', 'Item 2', 'Item 3', /* ... */];
const html = datos.map(item => `<li>${item}</li>`).join('');
document.querySelector('ul').innerHTML = html;
Técnica 3: Lazy Loading para imágenes
// Cargar imágenes solo cuando sean visibles
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
Técnica 4: Virtual Scrolling (para listas muy grandes)
// Solo renderizar los elementos visibles en viewport
function renderVisibleItems(scrollTop, itemHeight, totalItems) {
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + 20, totalItems); // 20 items visibles
// Renderizar solo items entre startIndex y endIndex
}
Usar textContent en lugar de innerHTML cuando sea posible (más seguro y rápido)
Desactivar eventos temporalmente durante operaciones pesadas
Usar requestAnimationFrame para animaciones
Limpiar event listeners cuando se eliminan elementos
Validar existencia de elementos antes de manipularlos
Ejercicio 6: Crear una función que genere una tabla de 1000 filas y 5 columnas con datos aleatorios, optimizando el rendimiento usando DocumentFragment.
Nombre del Taller: "Interfaz Interactiva de Gestión de Tareas"
Instrucciones: Crear una aplicación web completa que incluya:
Estructura HTML:
Un formulario para agregar tareas (input + botón)
Una lista <ul> para mostrar las tareas
Un contador que muestre el número de tareas pendientes
Filtros: "Todas", "Pendientes", "Completadas"
Funcionalidades JavaScript:
Agregar tareas al hacer clic en el botón o presionar Enter
Marcar tareas como completadas al hacer clic (tachar texto)
Eliminar tareas con un botón "X" en cada tarea
Filtrar tareas según el filtro seleccionado
Guardar las tareas en localStorage para persistencia
Mostrar mensaje si se intenta agregar tarea vacía
Requisitos Técnicos:
Usar querySelector y querySelectorAll para selección de elementos
Implementar delegación de eventos en la lista de tareas
Usar classList para agregar/quitar clases CSS
Manipular atributos con setAttribute y getAttribute
Optimizar la inserción de múltiples tareas usando DocumentFragment
Validar que el código no cause bloqueos de renderización
Estilos CSS:
Diseño responsivo básico
Animaciones suaves al agregar/eliminar tareas
Estados visuales para tareas completadas (tachado, color gris)
Hover effects en botones y elementos interactivos
Criterios de Evaluación:
Funcionalidad completa (40%)
Uso correcto de selección de elementos (15%)
Implementación de delegación de eventos (15%)
Manipulación eficiente del DOM (15%)
Código limpio y bien estructurado (15%)
Tiempo estimado: 45 minutos
MDN Web Docs - DOM API
"JavaScript: The Definitive Guide" - David Flanagan
"Eloquent JavaScript" - Marijn Haverbeke (capítulo 14)
"DOM Enlightenment" - Cody Lindley