Skip to content

Unidad 2: Manipulación Avanzada del DOM

Objetivo: Aprender a interactuar con el DOM (Document Object Model) para modificar el contenido, manejar eventos y optimizar la manipulación de los elementos de una página web.


2.1 Introducción al DOM

¿Qué vamos a aprender?

El DOM (Document Object Model) es la representación estructurada de un documento HTML en JavaScript. Permite acceder y modificar el contenido y la estructura de la página web de manera dinámica.

Para trabajar con el DOM, usaremos diferentes métodos de selección de elementos, eventos y modificaciones en la estructura del documento.

Ejemplo base de HTML

A lo largo de este tema, utilizaremos el siguiente archivo HTML como base para nuestras prácticas.

Ejemplo de documento HTML base

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Manipulación del DOM</title>
</head>
<body>
    <h1 id="titulo">Hola, mundo</h1>
    <p class="parrafo">Este es el primer párrafo.</p>
    <p class="parrafo">Este es el segundo párrafo.</p>
    <p class="parrafo">Este es el tercer párrafo.</p>
    <button id="boton">Haz clic</button>

    <script src="script.js"></script>
</body>
</html>
Este archivo contiene un título, varios párrafos y un botón que usaremos para nuestros ejemplos de manipulación del DOM.


2.2 Métodos de Selección de Elementos en el DOM

¿Qué vamos a aprender?

En esta sección veremos cómo seleccionar elementos del DOM utilizando los métodos más comunes: getElementById, getElementsByTagName, getElementsByClassName, querySelector y querySelectorAll.

getElementById

Selecciona un elemento por su id.

Ejemplo de getElementById

let titulo = document.getElementById("titulo");
console.log(titulo.textContent); // "Hola, mundo"
Este método devuelve un único elemento con el id especificado. En este caso, obtenemos el título y mostramos su contenido en la consola.

También podemos modificar el contenido de un elemento seleccionado con getElementById. para este tenemos dos opciones textContent y innerHTML. TextContent nos permite modificar el texto del elemento, mientras que innerHTML nos permite modificar el contenido HTML del elemento.

Ejemplos de cuando utilizar textContent o innerHTML:

  • textContent: Si solo necesitamos modificar el texto de un elemento, es recomendable usar textContent para evitar problemas de seguridad.
    • element.textContent = "Nuevo texto";
  • innerHTML: Si necesitamos modificar el contenido HTML de un elemento, podemos usar innerHTML para insertar etiquetas HTML. -element.innerHTML = "<strong>Nuevo texto</strong>";

También podemos acceder y modificar los atributos de un elemento con getAttribute y setAttribute. Vemos un ejemplo donde veremos modificaremos el valor de un atributo y añadiremos otro

Ejemplo de getAttribute y setAttribute

1
2
3
4
5
let titulo = document.getElementById("titulo");
console.log(titulo.getAttribute("id")); // "titulo"

titulo.setAttribute("class", "titulo-grande");
console.log(titulo.getAttribute("class")); // "titulo-grande"
En este ejemplo, obtenemos el atributo id del título y luego le añadimos la clase titulo-grande.

getElementsByTagName

Selecciona todos los elementos con una etiqueta específica y devuelve una colección HTML.

Ejemplo de getElementsByTagName modificando elementos

1
2
3
4
5
let parrafos = document.getElementsByTagName("p");

for (let i = 0; i < parrafos.length; i++) {
    parrafos[i].textContent = `Párrafo modificado ${i + 1}`;
}
En este ejemplo, recorremos todos los elementos <p> y modificamos su contenido dinámicamente, agregando un número a cada uno.

getElementsByClassName

Selecciona todos los elementos con una clase específica y devuelve una colección HTML. EL resto de la funcionalidad es similar a getElementsByTagName. Nos devuelve un array con todos los elementos que tengan la clase especificada.

Ejercicios

Ejercicios de selección de elementos

  1. Selecciona el botón por su id y cambia su texto a "Haz clic aquí".
  2. Selecciona un párrafo y modifica su contenido por un link a Google.
Solución

1
2
3
4
5
let boton = document.getElementById("boton");
boton.textContent = "Haz clic aquí";

let parrafo = document.querySelector(".parrafo");
parrafo.innerHTML = "<a href='https://www.google.com'>Google</a>";
En este ejemplo, seleccionamos el botón por su id y cambiamos su texto. Luego, seleccionamos un párrafo por su clase y modificamos su contenido por un link a Google.

Ejercicios de selección de elementos

  1. Selecciona todos los párrafos y cambia su color de fondo a amarillo.
  2. Selecciona todos los elementos con la clase parrafo y cambia su contenido por "Párrafo modificado".
Solución

1
2
3
4
5
6
let parrafos = document.querySelectorAll(".parrafo");

parrafos.forEach(parrafo => {
    parrafo.style.backgroundColor = "yellow";
    parrafo.textContent = "Párrafo modificado";
});
En este ejemplo, seleccionamos todos los párrafos con la clase parrafo y cambiamos su color de fondo a amarillo y su contenido por "Párrafo modificado".


2.3 Delegación de eventos

¿Qué vamos a aprender?

La delegación de eventos es una técnica que nos permite manejar eventos de múltiples elementos utilizando un solo addEventListener. Es especialmente útil cuando trabajamos con elementos dinámicos que se agregan al DOM después de que la página ha cargado.

Concepto y utilidad para optimización del código

En lugar de agregar un event listener a cada elemento individualmente, podemos agregarlo a un elemento contenedor y capturar eventos de sus hijos.

Ejemplo de Delegación de Eventos

1
2
3
4
5
document.body.addEventListener("click", function(event) {
    if (event.target.classList.contains("parrafo")) {
        event.target.style.color = "red";
    }
});
En este ejemplo, cualquier <p> dentro del body cambiará su color a rojo cuando se haga clic en él, sin necesidad de asignar un evento a cada párrafo de forma individual.

Ejercicios

Practica con delegación de eventos

  1. Crea una lista <ul> con varios <li> y usa delegación de eventos para cambiar su color cuando se haga clic en un elemento.
  2. Usa delegación de eventos para detectar cuándo se hace clic en el botón y cambiar su texto.
Solución
document.body.addEventListener("click", function(event) {
    if (event.target.tagName === "LI") {
        event.target.style.backgroundColor = "lightblue";
    }
});

let boton = document.getElementById("boton");
boton.addEventListener("click", function() {
    boton.textContent = "Clickeado";
});

2.4 MutationObserver: Detección de cambios en el DOM

¿Qué vamos a aprender?

El MutationObserver es una API de JavaScript que nos permite detectar cambios en la estructura del DOM en tiempo real. Se usa para observar la modificación de elementos, la inserción o eliminación de nodos y la actualización de atributos.

Introducción al API MutationObserver

Este API nos permite observar cambios en un elemento específico y ejecutar una función cuando ocurran.

sintaxis:

let observer = new MutationObserver(callback);
let config = { childList: true, subtree: true };
let targetNode = document.getElementById("elemento");
observer.observe(targetNode, config);

Los métodos y propiedades más importantes de MutationObserver son:

  • observe(target, config): Comienza a observar los cambios en el DOM.
  • disconnect(): Detiene la observación de cambios.
  • takeRecords(): Devuelve una lista de mutaciones pendientes.

Note

Añadir un observador a un elemento es igual que addEventListener, si usted observa el elemento múltiples veces no hace ninguna diferencia. Si se observa dos veces un elemento, el observe callback no se ejecutará dos veces, ni tampoco tendrá que ejecutar disconnect() dos veces. En otras palabras, una vez el elemento es observado, observarlo de nuevo con la misma instancia del observador no hará nada. Sin embargo, si el callback es diferente por supuesto se le añadirá otro observador.

El objeto que se pasa a la función callback, e del tipo mutationRecord, que contiene información sobre el cambio detectado en el DOM. Tiene las siguiente propiedades:

  • type: Tipo de mutación (attributes, childList, characterData).
  • target: Elemento que ha sido modificado.
  • addedNodes: Lista de nodos añadidos.
  • removedNodes: Lista de nodos eliminados.
  • attributeName: Nombre del atributo modificado.
  • oldValue: Valor anterior del atributo modificado.

callback: Función que se ejecuta cuando se detecta un cambio en el DOM.

La función que será llamada en cada mutación del DOM. El observer llamará a esta función con dos argumentos. El primero es un array de objetos, cada uno del tipo MutationRecord. El segundo es la propia instancia del MutationObserver.

Ejemplo de MutationObserver

let observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log("Cambio detectado:", mutation);
    });
});

let config = { childList: true, subtree: true };
let targetNode = document.getElementById("titulo");
observer.observe(targetNode, config);

// Simulamos un cambio en el DOM después de 3 segundos
setTimeout(() => {
    targetNode.textContent = "Texto Modificado";
}, 3000);
En este ejemplo, el MutationObserver detectará cualquier cambio en el contenido del elemento con id titulo y lo imprimirá en la consola.

Ejercicios

Práctica con MutationObserver

  1. Crea un MutationObserver que detecte cuando se agregue un nuevo párrafo a la página.
  2. Modifica el código para que, cuando detecte un cambio, agregue un mensaje en la consola indicando qué ha cambiado.
Solución
let observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log("Se ha detectado un cambio en: ", mutation.target);
    });
});

let config = { childList: true, subtree: true };
let body = document.body;
observer.observe(body, config);

// Simulamos la adición de un nuevo párrafo
setTimeout(() => {
    let nuevoParrafo = document.createElement("p");
    nuevoParrafo.textContent = "Este es un nuevo párrafo agregado dinámicamente.";
    document.body.appendChild(nuevoParrafo);
}, 3000);

2.5 Uso de Plantillas HTML Dinámicas

¿Qué vamos a aprender?

El uso de plantillas HTML dinámicas nos permite generar contenido de manera eficiente sin necesidad de manipular directamente el DOM con múltiples innerHTML o createElement. Para ello, utilizamos la etiqueta <template> y el DocumentFragment.

Uso de <template> e innerHTML

La etiqueta <template> nos permite definir fragmentos de HTML que no se renderizan en el DOM hasta que los clonamos y los insertamos manualmente.

Ejemplo de uso de <template>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Ejemplo de Template</title>
</head>
<body>
    <template id="mi-template">
        <div class="tarjeta">
            <h2 class="titulo"></h2>
            <p class="contenido"></p>
        </div>
    </template>
    <div id="contenedor"></div>
    <script src="script.js"></script>
</body>
</html>
En este ejemplo, la etiqueta <template> define una tarjeta con un título y un párrafo, pero no se renderiza en el DOM hasta que JavaScript la clona y la inserta.

Uso de DocumentFragment

DocumentFragment nos permite manipular grandes fragmentos de contenido antes de agregarlos al DOM, optimizando el rendimiento.

Ejemplo de DocumentFragment

document.addEventListener("DOMContentLoaded", () => {
    let template = document.getElementById("mi-template");
    let contenedor = document.getElementById("contenedor");
    let fragmento = document.createDocumentFragment();

    for (let i = 1; i <= 3; i++) {
        let clon = template.content.cloneNode(true);
        clon.querySelector(".titulo").textContent = `Título ${i}`;
        clon.querySelector(".contenido").textContent = `Contenido de la tarjeta ${i}`;
        fragmento.appendChild(clon);
    }

    contenedor.appendChild(fragmento);
});
Aquí clonamos la plantilla tres veces, modificamos su contenido y luego insertamos todo en el DOM de una sola vez con DocumentFragment para mejorar el rendimiento.

Ejercicios

Práctica con Plantillas HTML Dinámicas

  1. Modifica el ejemplo para agregar una imagen dentro de cada tarjeta.
  2. Crea un botón que, al hacer clic, agregue una nueva tarjeta con contenido dinámico.
Solución
1
2
3
4
5
6
7
8
document.getElementById("boton").addEventListener("click", () => {
    let template = document.getElementById("mi-template");
    let contenedor = document.getElementById("contenedor");
    let clon = template.content.cloneNode(true);
    clon.querySelector(".titulo").textContent = "Nueva Tarjeta";
    clon.querySelector(".contenido").textContent = "Esta tarjeta fue agregada dinámicamente.";
    contenedor.appendChild(clon);
});

2.6 Proyecto Final: Catálogo de Cursos Dinámico

Objetivo

Construir una aplicación web que muestre una lista de cursos de informática cargados dinámicamente desde un archivo JSON. Cada curso se mostrará como una tarjeta interactiva y permitirá ordenación y selección.

Requisitos

Cargar los cursos desde un JSON y mostrarlos en el DOM usando una plantilla HTML (<template>). ✅ Cada tarjeta debe contener: nombre del curso, tecnología, duración, precio, imagen y descripción. ✅ Al hacer clic en una tarjeta, se aplicará o quitará la clase selected. ✅ Agregar un selector (<select>) para ordenar las tarjetas por nombre, tecnología o duración.

Estructura del JSON (cursos.json)

[
    {
        "nombre": "Desarrollo Web con JavaScript",
        "tecnologia": "JavaScript",
        "duracion": "40 horas",
        "precio": "250€",
        "imagen": "js-course.jpg",
        "descripcion": "Aprende a desarrollar aplicaciones web dinámicas con JavaScript."
    },
    {
        "nombre": "Fundamentos de Python",
        "tecnologia": "Python",
        "duracion": "50 horas",
        "precio": "300€",
        "imagen": "python-course.jpg",
        "descripcion": "Curso para aprender los fundamentos del lenguaje Python."
    }
]

El fichero completo lo puedes descargar aquí.

Estructura base del HTML

Estructura base del HTML

<template id="curso-template">
    <div class="curso">
        <img class="imagen" src="" alt="">
        <h2 class="nombre"></h2>
        <p class="tecnologia"></p>
        <p class="duracion"></p>
        <p class="precio"></p>
        <p class="descripcion"></p>
    </div>
</template>
<select id="ordenar">
    <option value="nombre">Nombre</option>
    <option value="tecnologia">Tecnología</option>
    <option value="duracion">Duración</option>
</select>
<div id="contenedor-cursos"></div>

Pasos para la Implementación

1 Cargar el JSON y recorrer los cursos.
2 Clonar el template y completar la información con los datos del JSON.
3 Insertar las tarjetas en el contenedor usando DocumentFragment.
4 Implementar la selección de tarjetas al hacer clic.
5 Añadir funcionalidad para ordenar las tarjetas según la opción elegida.