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ícateCiclo 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.
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
Polimorfismo
Array
Transformación con map()
Gestor de tareas con JavaScript
Manipulación DOM
Funciones
Funciones flecha
Async / Await
Creación y uso de variables
Excepciones
Promises
Funciones cierre (closure)
Herencia
Herencia
Estructuras de control
Selección de elementos DOM
Modificación de elementos DOM
Filtrado con filter() y find()
Funciones cierre (closure)
Funciones
Mapas con Map
Reducción con reduce()
Callbacks
Manipulación DOM
Promises
Async / Await
Eventos del DOM
Introducción a JavaScript
Async / Await
Promises
Filtrado con filter() y find()
Callbacks
Creación de clases y objetos Restaurante
Reducción con reduce()
Filtrado con filter() y find()
Reducción con reduce()
Conjuntos con Set
Herencia de clases
Eventos del DOM
Clases y objetos
Modificación de elementos DOM
Mapas con Map
Introducción a JavaScript
Funciones
Tipos de datos
Clases y objetos
Array
Conjuntos con Set
Array
Encapsulación
Clases y objetos
Uso de operadores
Uso de operadores
Estructuras de control
Excepciones
Transformación con map()
Funciones flecha
Selección de elementos DOM
Encapsulación
Mapas con Map
Creación y uso de variables
Polimorfismo
Tipos de datos
Estructuras de control
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.
Introducción A Javascript
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Arrays Y Métodos
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Mapas Con Map
Estructuras De Datos
Funciones Flecha
Programación Funcional
Filtrado Con Filter() Y Find()
Programación Funcional
Transformación Con Map()
Programación Funcional
Reducción Con Reduce()
Programación Funcional
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Manipulación Dom
Dom
Selección De Elementos Dom
Dom
Modificación De Elementos Dom
Dom
Eventos Del Dom
Dom
Callbacks
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
Programación Asíncrona
En esta lección
Objetivos de aprendizaje de esta lección
- 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.
- Aprender cómo crear nuevos elementos HTML utilizando el método
document.createElement()
y cómo agregarlos al DOM utilizandoappendChild()
oinsertBefore()
. - Familiarizarse con las formas de modificar el contenido y los atributos de un nodo utilizando
textContent
,innerHTML
,setAttribute()
ygetAttribute()
. - 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. - Reconocer la importancia de la manipulación del DOM en JavaScript para crear sitios web interactivos y personalizados.