JavaScript
Tutorial JavaScript: Modificación de elementos DOM
JavaScript modificar DOM: técnicas efectivas. Aprende técnicas efectivas para modificar el DOM en JavaScript con ejemplos detallados.
Aprende JavaScript y certifícateAlteración de contenido textual: Diferencias entre innerText, textContent y nodeValue
Cuando trabajamos con el DOM, modificar el contenido textual de los elementos es una de las operaciones más comunes. JavaScript ofrece tres propiedades principales para manipular texto: innerText
, textContent
y nodeValue
. Aunque parecen similares, cada una tiene comportamientos específicos que debemos entender para elegir la más adecuada en cada situación.
innerText
La propiedad innerText
representa el contenido de texto visible de un elemento, respetando los estilos CSS aplicados. Esta propiedad:
- Ignora elementos ocultos con CSS (
display: none
) - Respeta saltos de línea del HTML renderizado
- Desencadena un recálculo de estilos al acceder a ella
- No incluye etiquetas HTML, solo texto
// Modificar el texto visible de un párrafo
const paragraph = document.querySelector('p');
paragraph.innerText = 'Este es el nuevo texto visible';
// Obtener solo el texto visible
const visibleText = document.querySelector('article').innerText;
innerText
es útil cuando necesitamos trabajar exclusivamente con el texto que el usuario puede ver en pantalla, pero tiene un impacto en rendimiento debido al recálculo de estilos.
textContent
La propiedad textContent
representa todo el contenido textual de un nodo y sus descendientes, independientemente de su visibilidad:
- Incluye el texto de todos los elementos hijos, incluso los ocultos
- Preserva espacios en blanco y formato original del código HTML
- Es más eficiente que innerText (no desencadena recálculo de estilos)
- Ignora etiquetas HTML, devolviendo solo texto
// Establecer el contenido textual completo
const div = document.querySelector('div');
div.textContent = 'Este texto reemplaza todo el contenido anterior';
// Obtener todo el texto, incluso el oculto
const allText = document.querySelector('section').textContent;
Esta propiedad es ideal cuando necesitamos acceder o modificar todo el contenido textual sin preocuparnos por la visibilidad o el formato visual.
nodeValue
A diferencia de las anteriores, nodeValue
es una propiedad de los nodos del DOM, no específicamente de los elementos. Para nodos de texto, contiene el texto del nodo:
- Solo funciona directamente en nodos de texto, no en elementos
- Es
null
cuando se aplica a elementos (nodos de tipo 1) - Requiere acceder primero al nodo de texto hijo
- Es más específico para manipulaciones precisas
// Acceder y modificar el texto de un nodo específico
const paragraph = document.querySelector('p');
const textNode = paragraph.firstChild; // Obtenemos el nodo de texto
// Solo funciona si firstChild es un nodo de texto
if (textNode && textNode.nodeType === 3) { // 3 = nodo de texto
textNode.nodeValue = 'Texto modificado con nodeValue';
}
nodeValue
es particularmente útil cuando necesitamos manipular nodos de texto específicos dentro de un elemento que contiene múltiples nodos hijos.
Comparación práctica
Veamos un ejemplo que ilustra las diferencias entre estas propiedades:
// Creamos un elemento con contenido mixto
const container = document.createElement('div');
container.innerHTML = 'Texto visible <span style="display:none">texto oculto</span> <b>texto en negrita</b>';
document.body.appendChild(container);
// Comparación de resultados
console.log('innerText:', container.innerText); // "Texto visible texto en negrita"
console.log('textContent:', container.textContent); // "Texto visible texto oculto texto en negrita"
// Para nodeValue, necesitamos acceder al primer nodo de texto
const firstTextNode = container.firstChild;
console.log('nodeValue del primer nodo:', firstTextNode.nodeValue); // "Texto visible "
Casos de uso recomendados
- Usa
innerText
cuando necesites obtener o establecer el texto tal como aparece visualmente en la página. - Usa
textContent
cuando necesites todo el contenido textual sin importar la visibilidad, o cuando el rendimiento sea importante. - Usa
nodeValue
cuando necesites manipular nodos de texto específicos dentro de un elemento con estructura compleja.
// Ejemplo: Actualizar un contador de notificaciones
function updateNotificationCount(count) {
const badge = document.querySelector('.notification-badge');
// Usamos textContent por rendimiento
badge.textContent = count > 99 ? '99+' : count.toString();
// Actualizamos la visibilidad basada en el contador
badge.style.display = count > 0 ? 'block' : 'none';
}
La elección entre estas propiedades dependerá del contexto específico de tu aplicación y de los requisitos de manipulación de texto que necesites implementar.
Gestión de atributos personalizados y dataset: Almacenamiento de metadatos en elementos
Los elementos HTML no solo contienen contenido visible, sino que también pueden almacenar metadatos adicionales a través de atributos. Estos metadatos pueden ser cruciales para la lógica de nuestra aplicación sin afectar directamente la presentación visual. JavaScript ofrece mecanismos específicos para gestionar estos atributos, especialmente los personalizados.
Atributos estándar vs personalizados
Los elementos HTML tienen atributos estándar como id
, class
o src
, pero a menudo necesitamos almacenar información adicional específica de nuestra aplicación:
// Manipulación de atributos estándar
const image = document.querySelector('img');
image.src = 'new-image.jpg';
image.alt = 'Descripción actualizada';
Para información personalizada, podemos usar atributos personalizados que siguen una convención específica.
Métodos para manipular atributos
JavaScript proporciona varios métodos fundamentales para trabajar con atributos:
getAttribute()
- Obtiene el valor de un atributosetAttribute()
- Establece o modifica un atributohasAttribute()
- Verifica si existe un atributoremoveAttribute()
- Elimina un atributo
const button = document.querySelector('button');
// Establecer un atributo personalizado
button.setAttribute('data-status', 'active');
// Verificar si existe
if (button.hasAttribute('data-status')) {
// Obtener su valor
const status = button.getAttribute('data-status');
console.log(`Estado actual: ${status}`);
}
// Eliminar el atributo
button.removeAttribute('data-status');
Estos métodos funcionan con cualquier atributo, pero para los personalizados existe una forma más estructurada.
Atributos data-* y la API dataset
HTML5 introdujo los atributos data-*
como una forma estandarizada para almacenar datos personalizados. Estos atributos:
- Comienzan siempre con el prefijo
data-
- Son completamente válidos en HTML
- No afectan la presentación visual
- Son accesibles a través de JavaScript
<article
data-category="technology"
data-published="2023-05-15"
data-author-id="42">
Contenido del artículo...
</article>
JavaScript proporciona la propiedad dataset
para acceder a estos atributos de forma más elegante:
const article = document.querySelector('article');
// Acceder a los atributos data-*
console.log(article.dataset.category); // "technology"
console.log(article.dataset.published); // "2023-05-15"
console.log(article.dataset.authorId); // "42" (note la conversión camelCase)
// Modificar valores
article.dataset.category = "programming";
article.dataset.views = "1250"; // Crea un nuevo atributo data-views
// Eliminar un atributo data-*
delete article.dataset.authorId;
Observa cómo los nombres de atributos con guiones (data-author-id
) se convierten automáticamente a camelCase (authorId
) en la propiedad dataset
.
Casos de uso prácticos
Los atributos personalizados y dataset
son especialmente útiles para:
- Almacenar estado de componentes de interfaz
- Vincular elementos DOM con datos de aplicación
- Configurar comportamientos específicos
- Mejorar la accesibilidad con información adicional
// Ejemplo: Sistema de filtrado de productos
const products = document.querySelectorAll('.product');
const filterButtons = document.querySelectorAll('.filter-btn');
filterButtons.forEach(button => {
button.addEventListener('click', () => {
const category = button.dataset.category;
products.forEach(product => {
// Comparamos la categoría del producto con la del botón
if (category === 'all' || product.dataset.category === category) {
product.style.display = 'block';
} else {
product.style.display = 'none';
}
});
});
});
Ventajas de dataset sobre atributos personalizados
Aunque podemos usar atributos personalizados con cualquier nombre, la API dataset
ofrece varias ventajas:
- Validación HTML5: Los atributos
data-*
son estándar y pasan la validación - Sintaxis más limpia: Acceso directo mediante la propiedad
dataset
- Organización: Agrupa todos los datos personalizados bajo un mismo namespace
- Rendimiento: Optimizado para operaciones frecuentes
// Comparación de sintaxis
// Con setAttribute/getAttribute
element.setAttribute('data-user-id', '123');
const userId = element.getAttribute('data-user-id');
// Con dataset (más conciso)
element.dataset.userId = '123';
const userId = element.dataset.userId;
Consideraciones importantes
Al trabajar con atributos personalizados y dataset, ten en cuenta:
- Los valores de
dataset
siempre son cadenas de texto - Para valores complejos, considera usar
JSON.stringify()
yJSON.parse()
- Los cambios en
dataset
se reflejan inmediatamente en el DOM - Los nombres de atributos son case-sensitive en JavaScript
// Almacenar datos complejos
const userInfo = {
id: 42,
permissions: ['read', 'write'],
lastLogin: new Date().toISOString()
};
// Convertir a string para almacenar
element.dataset.userInfo = JSON.stringify(userInfo);
// Recuperar y convertir de nuevo a objeto
const storedInfo = JSON.parse(element.dataset.userInfo);
console.log(storedInfo.permissions); // ['read', 'write']
Observando cambios en atributos
Para aplicaciones más complejas, podemos detectar cambios en atributos usando MutationObserver
:
// Observar cambios en atributos data-*
const targetElement = document.querySelector('#dynamic-component');
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'attributes' &&
mutation.attributeName.startsWith('data-')) {
console.log(`Atributo ${mutation.attributeName} modificado`);
// Lógica para reaccionar al cambio
}
});
});
observer.observe(targetElement, { attributes: true });
Los atributos personalizados y la API dataset
proporcionan una forma elegante y estándar de asociar metadatos con elementos DOM, facilitando la creación de interfaces dinámicas y manteniendo una clara separación entre contenido, presentación y comportamiento.
Manipulación de clases y estilos: Estrategias para cambios visuales dinámicos
La capacidad de modificar la apariencia visual de los elementos HTML de forma dinámica es una de las funcionalidades esenciales en el desarrollo web moderno. JavaScript ofrece diversas técnicas para manipular clases CSS y estilos en línea, permitiéndonos crear interfaces interactivas y reactivas sin necesidad de recargar la página.
Manipulación de clases con classList
La propiedad classList
proporciona una interfaz eficiente y potente para trabajar con las clases CSS de un elemento. Esta API ofrece métodos que simplifican la gestión de clases:
const card = document.querySelector('.card');
// Añadir una clase
card.classList.add('highlighted');
// Eliminar una clase
card.classList.remove('hidden');
// Alternar una clase (añade si no existe, elimina si existe)
card.classList.toggle('expanded');
// Verificar si contiene una clase específica
if (card.classList.contains('active')) {
console.log('La tarjeta está activa');
}
// Reemplazar una clase por otra
card.classList.replace('loading', 'loaded');
La API classList
es especialmente útil porque evita problemas comunes al manipular la propiedad className
directamente, como sobrescribir clases existentes o tener que dividir cadenas de texto.
Manipulación de múltiples clases
Podemos manipular varias clases simultáneamente, lo que resulta útil para componentes con estados complejos:
// Añadir múltiples clases
const modal = document.querySelector('.modal');
modal.classList.add('visible', 'animated', 'fade-in');
// Eliminar múltiples clases
const notification = document.querySelector('.notification');
notification.classList.remove('new', 'unread', 'priority');
Alternancia condicional con toggle
El método toggle
acepta un segundo parámetro opcional que determina si la clase debe añadirse o eliminarse:
const menuItem = document.querySelector('.menu-item');
// Añadir o eliminar la clase según una condición
const isUserLoggedIn = true;
menuItem.classList.toggle('restricted', !isUserLoggedIn);
// Equivalente a:
if (isUserLoggedIn) {
menuItem.classList.remove('restricted');
} else {
menuItem.classList.add('restricted');
}
Este patrón es extremadamente útil para sincronizar el estado visual con el estado lógico de la aplicación.
Manipulación directa de estilos en línea
Para cambios de estilo más específicos o dinámicos, podemos manipular directamente la propiedad style
del elemento:
const progressBar = document.querySelector('.progress-bar');
// Establecer una propiedad CSS individual
progressBar.style.width = '75%';
progressBar.style.backgroundColor = '#3498db';
// Establecer múltiples propiedades
const banner = document.querySelector('.banner');
Object.assign(banner.style, {
opacity: '0.9',
transform: 'translateY(0)',
transition: 'all 0.3s ease-in-out'
});
Las propiedades CSS con guiones se convierten a camelCase cuando se accede a través del objeto style
:
// Propiedades CSS con guiones
element.style.backgroundColor = '#f5f5f5'; // background-color
element.style.marginBottom = '20px'; // margin-bottom
element.style.borderRadius = '4px'; // border-radius
Obtención de estilos computados
Para leer los estilos actuales de un elemento (incluyendo los aplicados por hojas de estilo externas), usamos getComputedStyle()
:
const heading = document.querySelector('h1');
const styles = window.getComputedStyle(heading);
// Obtener valores específicos
const fontSize = styles.fontSize;
const color = styles.color;
// Usar valores para cálculos o lógica
const numericFontSize = parseFloat(fontSize);
const largerSize = `${numericFontSize * 1.2}px`;
Esta técnica es especialmente útil cuando necesitamos basar cambios dinámicos en los estilos actuales del elemento.
Estrategias para cambios visuales eficientes
1. Priorizar clases sobre estilos en línea
Las clases CSS ofrecen varias ventajas importantes:
// Enfoque menos eficiente: estilos en línea
function highlightElement(element) {
element.style.backgroundColor = '#fffde7';
element.style.border = '1px solid #ffd600';
element.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
}
// Enfoque más eficiente: alternar clases
function highlightElement(element) {
element.classList.add('highlighted');
}
Las clases permiten:
- Separar la lógica (JavaScript) de la presentación (CSS)
- Reutilizar estilos en múltiples elementos
- Aprovechar la especificidad y cascada de CSS
- Mejorar el rendimiento al minimizar repintados
2. Técnica de cambio de estado con atributos data-*
Combinar atributos data-*
con selectores CSS para crear máquinas de estado visuales:
// JavaScript: gestionar el estado
const dropdown = document.querySelector('.dropdown');
dropdown.addEventListener('click', () => {
// Alternar entre estados 'open' y 'closed'
const newState = dropdown.dataset.state === 'open' ? 'closed' : 'open';
dropdown.dataset.state = newState;
});
/* CSS: definir apariencia según el estado */
.dropdown[data-state="closed"] .dropdown-content {
display: none;
}
.dropdown[data-state="open"] .dropdown-content {
display: block;
animation: fadeIn 0.3s ease;
}
Esta técnica crea una interfaz declarativa entre el estado lógico y la presentación visual.
3. Animaciones basadas en clases
Las transiciones y animaciones CSS combinadas con manipulación de clases crean efectos suaves y eficientes:
function showNotification(message) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
document.body.appendChild(notification);
// Forzar un reflow antes de añadir la clase de animación
notification.offsetHeight;
// Añadir la clase que activa la animación
notification.classList.add('visible');
// Eliminar después de que termine la animación
setTimeout(() => {
notification.classList.remove('visible');
notification.addEventListener('transitionend', () => {
notification.remove();
});
}, 3000);
}
4. Cambios por lotes con requestAnimationFrame
Para cambios visuales complejos, agrupar modificaciones dentro de requestAnimationFrame
mejora el rendimiento:
function updateVisualElements(elements, values) {
requestAnimationFrame(() => {
elements.forEach((element, index) => {
// Aplicar todos los cambios de una vez
element.style.width = `${values[index]}%`;
element.classList.toggle('above-threshold', values[index] > 75);
});
});
}
Patrones prácticos para casos de uso comunes
Interfaz de pestañas (tabs)
function setupTabs() {
const tabButtons = document.querySelectorAll('.tab-button');
const tabPanels = document.querySelectorAll('.tab-panel');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
// Desactivar todas las pestañas
tabButtons.forEach(btn => btn.classList.remove('active'));
tabPanels.forEach(panel => panel.classList.remove('active'));
// Activar la pestaña seleccionada
const tabId = button.dataset.tabId;
button.classList.add('active');
document.querySelector(`.tab-panel[data-tab-id="${tabId}"]`)
.classList.add('active');
});
});
}
Tema claro/oscuro
function initThemeToggle() {
const themeToggle = document.querySelector('.theme-toggle');
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
// Establecer tema inicial basado en preferencia del sistema
document.body.classList.toggle('dark-theme', prefersDarkScheme.matches);
themeToggle.addEventListener('click', () => {
// Alternar tema
document.body.classList.toggle('dark-theme');
// Guardar preferencia
const isDark = document.body.classList.contains('dark-theme');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
}
Respuesta visual a interacciones
function setupButtonFeedback() {
const buttons = document.querySelectorAll('.action-button');
buttons.forEach(button => {
button.addEventListener('click', () => {
// Añadir clase para feedback visual
button.classList.add('clicked');
// Realizar la acción
performAction(button.dataset.action)
.then(() => {
// Indicar éxito
button.classList.remove('clicked');
button.classList.add('success');
})
.catch(() => {
// Indicar error
button.classList.remove('clicked');
button.classList.add('error');
})
.finally(() => {
// Limpiar clases después de un tiempo
setTimeout(() => {
button.classList.remove('success', 'error');
}, 2000);
});
});
});
}
La manipulación efectiva de clases y estilos es fundamental para crear interfaces dinámicas y responsivas. Al combinar estas técnicas con una estructura CSS bien planificada, podemos implementar cambios visuales complejos manteniendo un código limpio y de alto rendimiento.
Otros ejercicios de programación de JavaScript
Evalúa tus conocimientos de esta lección Modificación de elementos DOM con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Clases y objetos
Uso de operadores
Uso de operadores
Estructuras de control
Proyecto Manipulación DOM
Excepciones
Transformación con map()
Arrays y Métodos
Reto Métodos de Strings
Transformación con map()
Funciones flecha
Async / Await
Selección de elementos DOM
API Fetch
Encapsulación
Mapas con Map
Creación y uso de variables
Polimorfismo
Reto Funciones flecha
Tipos de datos
Reto Operadores avanzados
Promises
Reto Estructuras de control
Estructuras de control
Pruebas unitarias
Inmutabilidad y programación funcional pura
Funciones flecha
Polimorfismo
Reto Polimorfismo
Array
Transformación con map()
Reto Variables
Gestor de tareas con JavaScript
Proyecto Modificación de elementos DOM
Manipulación DOM
Funciones
Conjuntos con Set
Reto Prototipos y cadena de prototipos
Reto Encapsulación
Funciones flecha
Async / Await
Reto Excepciones
Reto Filtrado con filter() y find()
Creación y uso de variables
Excepciones
Promises
Funciones cierre (closure)
Reto Herencia
Herencia
Proyecto Eventos del DOM
Herencia
Selección de elementos DOM
Modificación de elementos DOM
Reto Clases y objetos
Filtrado con filter() y find()
Funciones cierre (closure)
Reto Destructuring de objetos y arrays
Callbacks
Funciones
Mapas con Map
Reducción con reduce()
Callbacks
Manipulación DOM
Introducción al DOM
Reto Funciones
Reto Funciones cierre (closure)
Promises
Reto Reducción con reduce()
Async / Await
Reto Estructuras de control
Eventos del DOM
Introducción a JavaScript
Async / Await
Promises
Selección de elementos DOM
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
Proyecto carrito compra agoodshop
Introducción a JavaScript
Reto Mapas con Map
Funciones
Proyecto administrador de contactos
Reto Expresiones regulares
Tipos de datos
Clases y objetos
Array
Conjuntos con Set
Array
Encapsulación
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
Métodos De Strings
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Operadores Avanzados
Sintaxis
Funciones
Sintaxis
Expresiones Regulares
Sintaxis
Estructuras De Control
Sintaxis
Arrays Y Métodos
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Mapas Con Map
Estructuras De Datos
Conjuntos Con Set
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
Funciones Flecha
Programación Funcional
Transformación Con Map()
Programación Funcional
Inmutabilidad Y Programación Funcional Pura
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
This Y Contexto
Programación Orientada A Objetos
Patrón De Módulos Y Namespace
Programación Orientada A Objetos
Prototipos Y Cadena De Prototipos
Programación Orientada A Objetos
Destructuring De Objetos Y Arrays
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
Localstorage Y Sessionstorage
Dom
Bom (Browser Object Model)
Dom
Callbacks
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
Programación Asíncrona
Promises
Programación Asíncrona
Api Fetch
Programación Asíncrona
Async / Await
Programación Asíncrona
Naturaleza De Js Y Event Loop
Programación Asíncrona
Callbacks
Programación Asíncrona
Websockets
Programación Asíncrona
Módulos En Es6
Construcción
Configuración De Bundlers Como Vite
Construcción
Eslint Y Calidad De Código
Construcción
Npm Y Dependencias
Construcción
Introducción A Pruebas En Js
Testing
Pruebas Unitarias
Testing
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender la importancia de la modificación del DOM en el desarrollo web.
- Conocer los distintos métodos de selección de elementos del DOM en JavaScript.
- Aprender cómo modificar atributos de elementos utilizando
setAttribute
,getAttribute
, y la notación de punto. - Familiarizarse con la propiedad
classList
para trabajar con clases de elementos. - Entender el uso de atributos de datos personalizados (
data-*
) y cómo acceder a ellos a través del objetodataset
. - Aprender a modificar el contenido de texto e HTML de un elemento utilizando
textContent
einnerHTML
. - Conocer cómo agregar y eliminar elementos del DOM mediante la creación de nuevos elementos y el uso de
appendChild
yremoveChild
.