Desarrollo Web

Manipulación del DOM con ejemplos reales en JavaScript

Jairo
6 min de lectura
Manipulación del DOM con ejemplos reales en JavaScript

Qué es el DOM y por qué dominarlo cambia tu forma de programar

Si el HTML es el “esqueleto” de tu web, el DOM es el árbol vivo que el navegador construye para que JavaScript pueda leerlo y modificarlo. Cuando empecé, lo que de verdad me desbloqueó fue entender que tocar nodos es tocar la interfaz en tiempo real: cada cambio se refleja sin recargar la página.

Con el DOM puedo seleccionar “cajitas” (nodos), leer o cambiar su contenido/atributos/estilos, escuchar eventos y crear o eliminar elementos bajo demanda. Esta base basta para construir desde una lista de tareas hasta un panel de filtros avanzado. En mi día a día, si quiero un elemento único, tiro de getElementById("id"); si necesito selectores CSS, uso querySelector o querySelectorAll. Y cuando solo voy a imprimir texto del usuario, textContent me evita sustos frente a innerHTML.

Idea clave: el DOM no es solo “modificar etiquetas”; es pensar en nodos + atributos + eventos con foco en UX, seguridad y rendimiento.

Seleccionar elementos como pro: getElementById vs querySelector(All)

  • getElementById("heroTitle") es directo y rápido para un único elemento con id conocido.
  • querySelector(".card.active [data-price]") es flexible: acepta cualquier selector CSS.
  • querySelectorAll(".todo-item") devuelve un NodeList estático (no “en vivo”), ideal para iterar.
<h1 id="heroTitle">DOM 101</h1>
<ul id="todos"></ul>

// Único por id const title = document.getElementById("heroTitle"); // Selectores CSS potentes const activePrice = document.querySelector(".card.active [data-price]"); const items = document.querySelectorAll(".todo-item"); // NodeList items.forEach(li => li.classList.add("highlight"));

Consejo personal: cuando sé el id, voy directo. Si la estructura puede cambiar, querySelector me da libertad para apuntar a clases, atributos ([data-*]) y jerarquías. Esto me ha evitado roturas al refactorizar HTML.

Cambiar contenido y atributos con seguridad: textContent, setAttribute

Si solo voy a escribir texto, uso textContent; mantiene el texto literal y previene inyecciones HTML. En cambio, innerHTML inyecta HTML (útil para plantillas controladas por mí, no para entradas del usuario).

title.textContent = "Manipulación del DOM: ejemplos reales"; // seguro para texto

// Atributos
title.setAttribute("data-section", "intro");
title.setAttribute("aria-live", "polite"); // accesibilidad básica

Mi regla práctica: “si no necesito etiquetas, no uso innerHTML”. Ese pequeño hábito me ha ahorrado bugs y riesgos de XSS.

Estilos y clases sin dolor: classList y .style

Prefiero clases a estilos inline. Con classList puedo alternar estados sin pelearme con CSS:

const panel = document.querySelector(".panel");
panel.classList.add("is-open");
panel.classList.toggle("is-animated");

// En emergencias puntuales:
panel.style.maxHeight = "320px"; // evita abusar de .style

Comparativa práctica (sin tabla):

  • classList: escalable, legible, centraliza estilos en CSS.
  • .style: útil para una excepción o cálculo dinámico puntual.
  • setAttribute("style", …): lo evito; sobreescribe todo el estilo inline.

Crear y montar nodos dinámicos: createElement, appendChild (mini-proyecto: lista de tareas)

Un flujo que uso a diario: crear → configurar → insertar. Es limpio y predecible.

<input id="todoInput" placeholder="Nueva tarea" />
<button id="addBtn">Añadir</button>
<ul id="todoList" aria-live="polite"></ul>

const input = document.getElementById("todoInput"); const addBtn = document.getElementById("addBtn"); const list = document.getElementById("todoList"); addBtn.addEventListener("click", () => { const text = input.value.trim(); if (!text) return; const li = document.createElement("li"); li.className = "todo-item"; li.textContent = text; // seguro const removeBtn = document.createElement("button"); removeBtn.className = "remove"; removeBtn.textContent = "✕"; removeBtn.setAttribute("aria-label", `Eliminar ${text}`); li.appendChild(removeBtn); list.appendChild(li); input.value = ""; input.focus(); });

Por qué funciona bien:

  • textContent evita HTML inesperado.
  • Botón de eliminar con label accesible.
  • Devolvemos el foco al input para flujos rápidos.

Yo suelo describirlo así: “Crear con createElement y montar con appendChild es mi combo para listas dinámicas; elimino con remove() cuando ya no se necesita”.

Eliminar y reemplazar elementos sin romper nada: remove, removeChild

list.addEventListener("click", (e) => {
  if (e.target.matches("button.remove")) {
    const li = e.target.closest("li");
    li.remove(); // simple y moderno
  }
});

Notas express:

  • element.remove() es directo (no necesita padre).
  • Si soportas navegadores muy viejos, parent.removeChild(child).
  • Para reemplazar, node.replaceWith(nuevoNodo) mantiene el flujo del documento.

Eventos y delegación en interfaces dinámicas

Cuando creo elementos nuevos, prefiero delegar en un contenedor estable. Así no registro listeners por cada nodo y mejoro rendimiento/memoria.

list.addEventListener("click", (e) => {
  if (e.target.matches(".todo-item")) {
    e.target.classList.toggle("done");
  }
});

Comparativa breve:

  • Listeners individuales: simple pero crece mal.
  • Delegación en el padre: escalable, perfecto para listas y grillas dinámicas.

Rendimiento y UX: reflow/paint, fragmentos de documento y requestAnimationFrame

Manipular el DOM puede disparar reflows (cambio de layout) y paints (repintado). Para suavizar:

  1. Agrupa inserciones con DocumentFragment:
const frag = document.createDocumentFragment();
for (let i = 0; i < 500; i++) {
  const li = document.createElement("li");
  li.textContent = `Item ${i + 1}`;
  frag.appendChild(li);
}
list.appendChild(frag); // un solo reflow grande, mejor que 500 pequeños

  1. Mide con performance.now():
const t0 = performance.now();
// ... crear/insertar muchos nodos
const t1 = performance.now();
console.log(`Render tomó ${(t1 - t0).toFixed(2)}ms`);

  1. Anima con cabeza usando requestAnimationFrame:
function fadeIn(el) {
  el.style.opacity = 0;
  let o = 0;
  function step() {
    o += 0.05;
    el.style.opacity = o;
    if (o < 1) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}

En pruebas reales, pasar de 500 inserciones una a una a usar DocumentFragment me redujo el tiempo de render de “se nota el lag” a “fluido”. No doy milisegundos exactos porque varían por equipo, pero el patrón se siente en cualquier máquina.

Accesibilidad al actualizar el DOM: foco y ARIA

Tres hábitos que aplico siempre que manipulo la interfaz:

  • Gestionar foco: tras acciones, devolver el foco donde acelere al usuario (input.focus() al añadir).
  • Roles y nombres accesibles: botones con aria-label explícito si el texto no es claro (el ✕ de eliminar).
  • Anunciar cambios: regiones con aria-live="polite" para que lectores de pantalla detecten nuevas tareas.

Resultado: menos frustración y una interfaz que “habla” cuando el DOM cambia.

Errores comunes y cómo los evité (con snippets)

  1. Usar innerHTML con texto de usuario
    Solución: textContent.
  2. // Malo si viene de usuario:
    list.innerHTML = `<li>${userText}</li>`; // riesgo XSS
    // Bueno:
    const li = document.createElement("li");
    li.textContent = userText;
    list.appendChild(li);
    
  3. Agregar estilos inline por todo lado
    Solución: usa classList y deja el CSS hacer su trabajo.
  4. Listeners por cada elemento
    Solución: delegación en el contenedor.
  5. Forzar layout sin querer
    Solución: agrupa lecturas y escrituras; usa DocumentFragment y mide con performance.now().

Conclusiones + checklist rápida para producción

  • DOM = árbol vivo; cambia la interfaz sin recargar.
  • Selección: getElementById (rápido y directo) vs querySelector (flexible).
  • Contenido: textContent por defecto; innerHTML solo con HTML controlado.
  • Dinamismo: patrón crear → configurar → insertar; elimina con remove().
  • Escala: delegación de eventos y DocumentFragment.
  • UX: foco, ARIA y animaciones con requestAnimationFrame.

Checklist: ¿mides con performance.now()? ¿evitas innerHTML con texto de usuario? ¿usaste aria-* y foco? ¿centralizaste estilos con clases?


JavaScript

Jairo

Comentarios (0)

No hay comentarios aún. ¡Sé el primero en comentar!

Envíame un comentario

🍪 Utilizamos cookies para mejorar tu experiencia de navegación, analizar el tráfico del sitio y personalizar el contenido. Al continuar navegando, aceptas nuestro uso de cookies. Más información