JavaScript

JavaScript

Tutorial JavaScript: Manipulación DOM

JavaScript manipulación DOM: cómo hacerlo. Aprende a manipular el DOM en JavaScript con ejemplos prácticos y detallados.

Aprende JavaScript y certifícate

Ciclo de vida de los elementos: Creación, inserción y destrucción programática de nodos

Cuando trabajamos con el DOM (Document Object Model), los elementos HTML pasan por diferentes etapas durante su existencia en la página. Este ciclo de vida comprende desde la creación programática de un elemento hasta su eventual eliminación del árbol DOM. Entender este proceso es fundamental para manipular dinámicamente el contenido de nuestras páginas web.

Creación de elementos

Para crear nuevos elementos en JavaScript, utilizamos métodos específicos del DOM que nos permiten generar nodos desde cero. El método principal es document.createElement(), que crea un elemento HTML del tipo especificado.

// Creación de un elemento párrafo
const paragraph = document.createElement('p');

// Creación de un elemento botón
const button = document.createElement('button');

Una vez creado el elemento, podemos configurar sus atributos y contenido antes de insertarlo en el documento:

// Configurando atributos y contenido
paragraph.textContent = 'This is a dynamic paragraph';
paragraph.className = 'info-text';
paragraph.id = 'dynamic-paragraph';

button.textContent = 'Click me';
button.setAttribute('data-action', 'submit');

También podemos crear nodos de texto independientes que luego se pueden añadir a elementos:

const textNode = document.createTextNode('This is a text node');
paragraph.appendChild(textNode);

Para elementos más complejos, podemos utilizar innerHTML o insertAdjacentHTML(), aunque debemos tener cuidado con posibles vulnerabilidades XSS:

// Usando innerHTML (con precaución)
const container = document.createElement('div');
container.innerHTML = '<span class="highlight">Important information</span>';

Inserción de elementos

Una vez creado un elemento, necesitamos insertarlo en el DOM para que sea visible. Existen varios métodos de inserción que nos permiten colocar el elemento en diferentes posiciones:

  • appendChild(): Añade un nodo como último hijo de un elemento
// Añadir al final de un elemento contenedor
const container = document.getElementById('container');
container.appendChild(paragraph);
  • insertBefore(): Inserta un nodo antes de otro nodo de referencia
// Insertar antes de un elemento específico
const referenceNode = document.getElementById('reference');
container.insertBefore(button, referenceNode);
  • Métodos modernos de inserción: JavaScript moderno ofrece métodos más intuitivos
// Insertar después de un elemento específico
referenceNode.after(paragraph);

// Insertar antes de un elemento específico
referenceNode.before(button);

// Reemplazar un elemento existente
referenceNode.replaceWith(paragraph);
  • append() y prepend(): Permiten añadir uno o varios nodos al principio o final
// Añadir múltiples elementos al final
container.append(paragraph, button, textNode);

// Añadir al principio del contenedor
container.prepend(button);

Destrucción y eliminación de nodos

Cuando un elemento ya no es necesario, podemos eliminarlo del DOM. Esto es importante para liberar recursos y mantener el documento limpio:

  • remove(): Elimina el elemento directamente
// Eliminar un elemento
paragraph.remove();
  • removeChild(): Elimina un hijo desde el padre
// Eliminar un hijo desde el elemento padre
container.removeChild(button);
  • Reemplazo: Podemos reemplazar elementos en lugar de eliminarlos
// Reemplazar un elemento con otro
const newElement = document.createElement('span');
newElement.textContent = 'Replacement element';
button.replaceWith(newElement);
  • Limpieza de contenido: Para eliminar todo el contenido de un elemento
// Método moderno para limpiar contenido
container.innerHTML = '';

// Alternativa más eficiente para eliminar todos los hijos
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

Ciclo completo: Ejemplo práctico

Veamos un ejemplo que muestra el ciclo completo de un elemento, desde su creación hasta su eliminación:

// Función que crea un elemento de notificación temporal
function showTemporaryNotification(message, duration = 3000) {
  // 1. Creación
  const notification = document.createElement('div');
  notification.className = 'notification';
  notification.textContent = message;
  
  // 2. Inserción
  document.body.appendChild(notification);
  
  // 3. Destrucción programada
  setTimeout(() => {
    notification.remove();
  }, duration);
  
  return notification;
}

// Uso de la función
const myNotification = showTemporaryNotification('Operation completed!');

Este ejemplo ilustra cómo un elemento pasa por las tres fases principales: se crea con propiedades específicas, se inserta en el DOM haciéndolo visible, y finalmente se elimina automáticamente después de un tiempo determinado.

Consideraciones de rendimiento

Al manipular el DOM programáticamente, es importante considerar el rendimiento:

  • Minimizar el número de operaciones de inserción/eliminación, ya que cada una provoca un repintado del navegador
  • Crear todos los elementos necesarios antes de insertarlos en el DOM
  • Considerar el uso de fragmentos de documento para operaciones por lotes (tema que se abordará en la siguiente sección)
  • Desconectar elementos del DOM antes de realizar múltiples modificaciones
// Ejemplo de optimización básica
function addMultipleItems(container, items) {
  // Desconectar temporalmente del DOM
  const parent = container.parentNode;
  const nextSibling = container.nextSibling;
  parent.removeChild(container);
  
  // Realizar modificaciones
  items.forEach(item => {
    const element = document.createElement('li');
    element.textContent = item;
    container.appendChild(element);
  });
  
  // Reconectar al DOM
  parent.insertBefore(container, nextSibling);
}

Entender el ciclo de vida de los elementos DOM nos permite crear interfaces dinámicas y reactivas, añadiendo y eliminando contenido según las necesidades de nuestra aplicación, sin tener que recargar la página completa.

Fragmentos de documento: Optimización de rendimiento en operaciones por lotes

Cuando manipulamos el DOM realizando múltiples operaciones de inserción, nos enfrentamos a un problema de rendimiento significativo. Cada vez que añadimos un elemento al DOM, el navegador debe recalcular estilos, realizar el layout y repintar la pantalla, lo que puede provocar una experiencia lenta y entrecortada para el usuario, especialmente cuando insertamos muchos elementos consecutivamente.

Los fragmentos de documento (DocumentFragment) son una solución elegante a este problema. Un fragmento actúa como un contenedor ligero que almacena nodos DOM temporalmente, pero con una característica especial: cuando se inserta en el DOM, su contenido se transfiere directamente, sin incluir el propio fragmento.

Creación y uso básico de DocumentFragment

Para crear un fragmento de documento utilizamos el método createDocumentFragment():

// Crear un fragmento de documento vacío
const fragment = document.createDocumentFragment();

La ventaja principal es que podemos añadir múltiples elementos al fragmento sin tocar el DOM real, y luego insertar todo el conjunto en una única operación:

// Crear un fragmento y añadirle elementos
const fragment = document.createDocumentFragment();

// Añadir múltiples elementos al fragmento
for (let i = 0; i < 100; i++) {
  const listItem = document.createElement('li');
  listItem.textContent = `Item ${i}`;
  fragment.appendChild(listItem);
}

// Insertar todo el contenido en una sola operación
document.getElementById('myList').appendChild(fragment);

Comparación de rendimiento

Para entender el impacto de los fragmentos, comparemos dos enfoques para añadir 1000 elementos a una lista:

  • Enfoque ineficiente (sin fragmentos):
function addItemsInefficiently(count) {
  const list = document.getElementById('myList');
  
  for (let i = 0; i < count; i++) {
    const item = document.createElement('li');
    item.textContent = `Item ${i}`;
    list.appendChild(item); // ¡Provoca repintado en cada iteración!
  }
}
  • Enfoque optimizado (con fragmentos):
function addItemsEfficiently(count) {
  const list = document.getElementById('myList');
  const fragment = document.createDocumentFragment();
  
  for (let i = 0; i < count; i++) {
    const item = document.createElement('li');
    item.textContent = `Item ${i}`;
    fragment.appendChild(item); // No afecta al DOM real
  }
  
  list.appendChild(fragment); // Un solo repintado
}

La diferencia de rendimiento puede ser dramática, especialmente en dispositivos móviles o con DOM complejos. El enfoque con fragmentos puede ser hasta 10 veces más rápido en algunos casos.

Casos de uso prácticos

Los fragmentos son particularmente útiles en escenarios como:

  • Renderizado de listas largas de elementos:
function renderDataList(dataArray) {
  const fragment = document.createDocumentFragment();
  const list = document.getElementById('dataList');
  
  dataArray.forEach(item => {
    const listItem = document.createElement('li');
    listItem.textContent = item.name;
    listItem.dataset.id = item.id;
    fragment.appendChild(listItem);
  });
  
  list.appendChild(fragment);
}
  • Construcción de tablas dinámicas:
function createTableRows(userData) {
  const fragment = document.createDocumentFragment();
  const tbody = document.querySelector('tbody');
  
  userData.forEach(user => {
    const row = document.createElement('tr');
    
    const nameCell = document.createElement('td');
    nameCell.textContent = user.name;
    
    const emailCell = document.createElement('td');
    emailCell.textContent = user.email;
    
    row.appendChild(nameCell);
    row.appendChild(emailCell);
    fragment.appendChild(row);
  });
  
  tbody.appendChild(fragment);
}
  • Actualización de interfaces complejas:
function updateDashboard(metrics) {
  const fragment = document.createDocumentFragment();
  const container = document.getElementById('metricsContainer');
  
  // Limpiar contenedor existente
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }
  
  // Crear nuevos widgets de métricas
  Object.entries(metrics).forEach(([key, value]) => {
    const widget = document.createElement('div');
    widget.className = 'metric-widget';
    
    const label = document.createElement('h3');
    label.textContent = key;
    
    const valueElement = document.createElement('p');
    valueElement.textContent = value;
    
    widget.appendChild(label);
    widget.appendChild(valueElement);
    fragment.appendChild(widget);
  });
  
  container.appendChild(fragment);
}

Fragmentos y plantillas HTML

Los fragmentos se combinan perfectamente con el elemento <template>, que permite definir estructuras HTML reutilizables:

function renderProductList(products) {
  const template = document.getElementById('product-template');
  const fragment = document.createDocumentFragment();
  const container = document.getElementById('products');
  
  products.forEach(product => {
    // Clonar el contenido de la plantilla
    const clone = template.content.cloneNode(true);
    
    // Personalizar el clon
    clone.querySelector('.product-name').textContent = product.name;
    clone.querySelector('.product-price').textContent = `$${product.price}`;
    clone.querySelector('img').src = product.imageUrl;
    
    // Añadir al fragmento
    fragment.appendChild(clone);
  });
  
  // Insertar todo en una sola operación
  container.appendChild(fragment);
}

Técnicas avanzadas con fragmentos

  • Procesamiento por lotes para conjuntos de datos muy grandes:
function renderLargeDataset(items, batchSize = 500) {
  const container = document.getElementById('container');
  let index = 0;
  
  function processBatch() {
    // Crear un fragmento para este lote
    const fragment = document.createDocumentFragment();
    
    // Procesar elementos del lote actual
    const end = Math.min(index + batchSize, items.length);
    for (let i = index; i < end; i++) {
      const div = document.createElement('div');
      div.textContent = items[i];
      fragment.appendChild(div);
    }
    
    // Añadir el lote al DOM
    container.appendChild(fragment);
    
    // Actualizar índice
    index = end;
    
    // Programar el siguiente lote o terminar
    if (index < items.length) {
      requestAnimationFrame(processBatch);
    }
  }
  
  // Iniciar el procesamiento
  requestAnimationFrame(processBatch);
}
  • Construcción de interfaces complejas por capas:
function buildComplexUI(data) {
  // Fragmento principal
  const mainFragment = document.createDocumentFragment();
  
  // Construir sección de cabecera
  const headerFragment = document.createDocumentFragment();
  // ... añadir elementos a headerFragment
  
  // Construir sección de contenido
  const contentFragment = document.createDocumentFragment();
  // ... añadir elementos a contentFragment
  
  // Construir sección de pie
  const footerFragment = document.createDocumentFragment();
  // ... añadir elementos a footerFragment
  
  // Combinar todos los fragmentos
  const header = document.createElement('header');
  header.appendChild(headerFragment);
  
  const content = document.createElement('main');
  content.appendChild(contentFragment);
  
  const footer = document.createElement('footer');
  footer.appendChild(footerFragment);
  
  // Añadir al fragmento principal
  mainFragment.appendChild(header);
  mainFragment.appendChild(content);
  mainFragment.appendChild(footer);
  
  // Insertar todo en el DOM
  document.body.appendChild(mainFragment);
}

Los fragmentos de documento son una herramienta esencial para desarrolladores que buscan crear interfaces web de alto rendimiento. Al minimizar las operaciones de repintado y recálculo de layout, conseguimos una experiencia de usuario más fluida y reducimos significativamente el tiempo de carga percibido, especialmente en aplicaciones con actualizaciones frecuentes del DOM.

Manipulación de la estructura DOM: Reordenamiento y movimiento de elementos existentes

Una vez que los elementos están en el DOM, a menudo necesitamos reorganizarlos sin tener que destruirlos y recrearlos. JavaScript nos ofrece métodos eficientes para mover elementos existentes, lo que resulta más óptimo que el ciclo completo de eliminación y creación.

Movimiento de elementos

Mover un elemento en el DOM es sorprendentemente sencillo. Cuando insertamos un elemento que ya existe en el documento, este se elimina automáticamente de su posición original y se coloca en la nueva ubicación.

// Seleccionamos el elemento que queremos mover
const element = document.getElementById('moveMe');

// Seleccionamos el nuevo contenedor
const newParent = document.getElementById('newContainer');

// Al añadirlo al nuevo contenedor, se elimina automáticamente de su ubicación anterior
newParent.appendChild(element);

Este comportamiento es una característica fundamental del DOM que nos permite realizar operaciones de movimiento sin pasos intermedios.

Métodos modernos para reordenar elementos

JavaScript moderno ofrece varios métodos intuitivos para reordenar elementos:

  • append(): Añade elementos al final del contenedor
// Mover varios elementos al final de un contenedor
const container = document.querySelector('.container');
const item1 = document.getElementById('item1');
const item2 = document.getElementById('item2');

container.append(item1, item2); // Ambos elementos se mueven al final
  • prepend(): Añade elementos al principio del contenedor
// Mover un elemento al principio
const firstItem = document.getElementById('firstItem');
container.prepend(firstItem); // El elemento se mueve al principio
  • before() y after(): Posicionan elementos relativos a otro
// Mover un elemento antes de otro
const referenceElement = document.querySelector('.reference');
const moveElement = document.getElementById('moveMe');

referenceElement.before(moveElement); // Coloca moveElement antes de referenceElement

Intercambio de posiciones

Para intercambiar la posición de dos elementos, podemos usar un enfoque con un marcador temporal:

function swapElements(elem1, elem2) {
  // Crear un marcador temporal (comentario vacío)
  const temp = document.createComment('');
  
  // Reemplazar elem1 con el marcador
  elem1.replaceWith(temp);
  
  // Reemplazar elem2 con elem1
  elem2.replaceWith(elem1);
  
  // Reemplazar el marcador con elem2
  temp.replaceWith(elem2);
}

// Uso
const firstCard = document.querySelector('.card:first-child');
const lastCard = document.querySelector('.card:last-child');
swapElements(firstCard, lastCard);

Reordenamiento de listas

Un caso común es reordenar elementos en una lista, como mover un elemento hacia arriba o abajo:

function moveUp(item) {
  const previousSibling = item.previousElementSibling;
  
  if (previousSibling) {
    // Si hay un elemento anterior, intercambiamos posiciones
    previousSibling.before(item);
    return true;
  }
  
  return false; // Ya está en la primera posición
}

function moveDown(item) {
  const nextSibling = item.nextElementSibling;
  
  if (nextSibling) {
    // Si hay un elemento siguiente, intercambiamos posiciones
    nextSibling.after(item);
    return true;
  }
  
  return false; // Ya está en la última posición
}

Ordenación de elementos

Podemos implementar algoritmos de ordenación para reorganizar elementos según criterios específicos:

function sortListItems(listElement, compareFunction) {
  // Convertir la colección de nodos a array para poder ordenarla
  const items = Array.from(listElement.children);
  
  // Ordenar el array según la función de comparación
  items.sort(compareFunction);
  
  // Crear un fragmento para la operación por lotes
  const fragment = document.createDocumentFragment();
  
  // Añadir los elementos ordenados al fragmento
  items.forEach(item => fragment.appendChild(item));
  
  // Reemplazar el contenido de la lista con los elementos ordenados
  listElement.appendChild(fragment);
}

// Ejemplo: ordenar elementos por su contenido de texto
const list = document.getElementById('sortableList');
sortListItems(list, (a, b) => a.textContent.localeCompare(b.textContent));

Implementación de funcionalidad drag-and-drop

Una aplicación avanzada del reordenamiento es implementar funcionalidad de arrastrar y soltar:

function enableDragSort(listElement) {
  let draggedItem = null;
  
  // Configurar eventos para cada elemento de la lista
  Array.from(listElement.children).forEach(item => {
    // Hacer el elemento arrastrable
    item.draggable = true;
    
    // Cuando comienza el arrastre
    item.addEventListener('dragstart', () => {
      draggedItem = item;
      setTimeout(() => {
        item.classList.add('dragging');
      }, 0);
    });
    
    // Cuando termina el arrastre
    item.addEventListener('dragend', () => {
      draggedItem = null;
      item.classList.remove('dragging');
    });
    
    // Cuando otro elemento se arrastra sobre este
    item.addEventListener('dragover', e => {
      e.preventDefault(); // Necesario para permitir soltar
      
      if (item !== draggedItem) {
        const rect = item.getBoundingClientRect();
        const y = e.clientY - rect.top;
        
        // Determinar si colocar antes o después
        if (y < rect.height / 2) {
          item.before(draggedItem);
        } else {
          item.after(draggedItem);
        }
      }
    });
  });
}

// Activar la funcionalidad en una lista
enableDragSort(document.getElementById('draggableList'));

Animación de movimientos

Para mejorar la experiencia de usuario, podemos animar los movimientos de elementos usando CSS transitions:

function animatedMove(element, destination) {
  // 1. Calcular posición actual
  const startRect = element.getBoundingClientRect();
  
  // 2. Mover el elemento a su destino
  destination.appendChild(element);
  
  // 3. Calcular nueva posición
  const endRect = element.getBoundingClientRect();
  
  // 4. Calcular la diferencia
  const deltaX = startRect.left - endRect.left;
  const deltaY = startRect.top - endRect.top;
  
  // 5. Aplicar transformación inversa para simular posición original
  element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
  
  // 6. Forzar repintado
  element.offsetHeight;
  
  // 7. Añadir transición y eliminar transformación
  element.style.transition = 'transform 0.3s ease-out';
  element.style.transform = '';
  
  // 8. Limpiar después de la animación
  element.addEventListener('transitionend', function cleanup() {
    element.style.transition = '';
    element.removeEventListener('transitionend', cleanup);
  });
}

Consideraciones de rendimiento

Al reordenar elementos, debemos tener en cuenta algunas consideraciones de rendimiento:

  • Cada operación de movimiento provoca un recálculo de layout, así que minimiza el número de operaciones
  • Usa requestAnimationFrame para operaciones visuales complejas
  • Para reordenamientos masivos, considera desconectar temporalmente el contenedor del DOM
function reorderManyItems(container, newOrder) {
  // Guardar referencia al padre y hermano siguiente
  const parent = container.parentNode;
  const nextSibling = container.nextSibling;
  
  // Desconectar del DOM
  parent.removeChild(container);
  
  // Realizar reordenamiento (newOrder contiene los IDs en el nuevo orden)
  newOrder.forEach(id => {
    const element = document.getElementById(id);
    container.appendChild(element);
  });
  
  // Reconectar al DOM
  parent.insertBefore(container, nextSibling);
}

La capacidad de reordenar y mover elementos existentes es una habilidad fundamental para crear interfaces dinámicas y reactivas. Dominar estas técnicas nos permite implementar funcionalidades como listas ordenables, paneles reorganizables y otras interacciones que mejoran significativamente la experiencia de usuario sin sacrificar el rendimiento.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende JavaScript online

Ejercicios de esta lección Manipulación DOM

Evalúa tus conocimientos de esta lección Manipulación DOM con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones flecha

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Gestor de tareas con JavaScript

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Herencia

JavaScript
Puzzle

Herencia

JavaScript
Test

Estructuras de control

JavaScript
Código

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Promises

JavaScript
Test

Async / Await

JavaScript
Test

Eventos del DOM

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Promises

JavaScript
Puzzle

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Test

Funciones

JavaScript
Código

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

Array

JavaScript
Puzzle

Encapsulación

JavaScript
Puzzle

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Selección de elementos DOM

JavaScript
Puzzle

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Tipos de datos

JavaScript
Puzzle

Estructuras de control

JavaScript
Puzzle

Todas las lecciones de JavaScript

Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a JavaScript y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender qué es el Modelo de Objetos del Documento (DOM) y cómo representa la estructura de una página web en forma de árbol.
  2. Aprender cómo crear nuevos elementos HTML utilizando el método document.createElement() y cómo agregarlos al DOM utilizando appendChild() o insertBefore().
  3. Familiarizarse con las formas de modificar el contenido y los atributos de un nodo utilizando textContent, innerHTML, setAttribute() y getAttribute().
  4. Conocer el método removeChild() para eliminar elementos del DOM y la importancia de guardar el nodo eliminado en una variable si se desea restaurarlo más tarde.
  5. Reconocer la importancia de la manipulación del DOM en JavaScript para crear sitios web interactivos y personalizados.