DOM (Document Object Model)
¿Qué es el DOM?
El DOM (Document Object Model) es una representación estructurada en memoria de un documento HTML o XML que el navegador crea cuando carga una página web. Funciona como un puente entre el HTML estático y JavaScript dinámico, convirtiendo cada elemento, atributo y texto en objetos que pueden ser manipulados programáticamente.
¿Para qué se utiliza el DOM?
El DOM es utilizado por desarrolladores para:
- Acceder y modificar contenido de páginas web en tiempo real.
- Cambiar estilos, atributos y propiedades de elementos HTML.
- Responder a eventos del usuario como clics, teclado y movimientos del mouse.
- Crear, insertar y eliminar elementos dinámicamente.
- Implementar interfaces de usuario interactivas y aplicaciones web modernas.
- Construir single-page applications (SPAs) y aplicaciones reactivas.
¿Cómo funciona?
El DOM funciona como un árbol genealógico de tu página web - cada elemento HTML es un nodo que puede tener padres, hijos y hermanos. JavaScript puede navegar por este árbol familiar, modificar a cualquier miembro, o incluso añadir nuevos miembros a la familia en tiempo real.
Estructura del DOM: el árbol de nodos
El DOM convierte HTML en una jerarquía de objetos:
<!DOCTYPE html>
<html>
<head>
<title>Mi Página</title>
</head>
<body>
<div id="container">
<h1>Título Principal</h1>
<p class="intro">Párrafo de introducción</p>
<ul>
<li>Elemento 1</li>
<li>Elemento 2</li>
</ul>
</div>
</body>
</html>
Se convierte en esta estructura de árbol DOM:
Document
└── html (HTMLHtmlElement)
├── head (HTMLHeadElement)
│ └── title (HTMLTitleElement)
│ └── "Mi Página" (Text Node)
└── body (HTMLBodyElement)
└── div#container (HTMLDivElement)
├── h1 (HTMLHeadingElement)
│ └── "Título Principal" (Text Node)
├── p.intro (HTMLParagraphElement)
│ └── "Párrafo de introducción" (Text Node)
└── ul (HTMLUListElement)
├── li (HTMLLIElement)
│ └── "Elemento 1" (Text Node)
└── li (HTMLLIElement)
└── "Elemento 2" (Text Node)
Tipos de nodos en el DOM
// Los diferentes tipos de nodos DOM
const nodeTypes = {
ELEMENT_NODE: 1, // <div>, <p>, <span>, etc.
ATTRIBUTE_NODE: 2, // class="example", id="main"
TEXT_NODE: 3, // Contenido de texto
CDATA_SECTION_NODE: 4, // <![CDATA[...]]>
PROCESSING_INSTRUCTION_NODE: 7, // <?xml-stylesheet...?>
COMMENT_NODE: 8, // <!-- comentario -->
DOCUMENT_NODE: 9, // document
DOCUMENT_TYPE_NODE: 10, // <!DOCTYPE html>
DOCUMENT_FRAGMENT_NODE: 11, // DocumentFragment
};
// Ejemplo de inspección de nodos
function inspectNode(node) {
console.log('Tipo de nodo:', node.nodeType);
console.log('Nombre del nodo:', node.nodeName);
console.log('Valor del nodo:', node.nodeValue);
switch (node.nodeType) {
case Node.ELEMENT_NODE:
console.log('Es un elemento:', node.tagName);
console.log(
'Atributos:',
[...node.attributes].map((attr) => `${attr.name}="${attr.value}"`)
);
break;
case Node.TEXT_NODE:
console.log('Es texto:', `"${node.textContent}"`);
break;
case Node.COMMENT_NODE:
console.log('Es comentario:', node.data);
break;
}
}
// Uso
const element = document.getElementById('container');
inspectNode(element);
Selección de elementos: encontrar lo que necesitas
// Métodos de selección básicos
const byId = document.getElementById('container');
const byClass = document.getElementsByClassName('intro');
const byTag = document.getElementsByTagName('p');
const byName = document.getElementsByName('username');
// Selectores CSS modernos (más poderosos)
const single = document.querySelector('#container .intro');
const multiple = document.querySelectorAll('li');
const complex = document.querySelectorAll('div[data-active="true"] > p:first-child');
// Navegación por el árbol DOM
const element = document.querySelector('#container');
// Relaciones familiares
console.log('Padre:', element.parentNode);
console.log('Hijos:', element.children); // Solo elementos
console.log('Todos los nodos hijos:', element.childNodes); // Incluye texto
console.log('Primer hijo:', element.firstElementChild);
console.log('Último hijo:', element.lastElementChild);
console.log('Hermano siguiente:', element.nextElementSibling);
console.log('Hermano anterior:', element.previousElementSibling);
// Búsqueda hacia arriba en el árbol
const closest = element.closest('.parent-class'); // Busca ancestro
const matches = element.matches('.some-class'); // Verifica si coincide
// Función helper para navegación
function findAncestor(element, selector) {
while (element && element !== document) {
if (element.matches && element.matches(selector)) {
return element;
}
element = element.parentNode;
}
return null;
}
// Selección avanzada con filtros
function selectElements(selector, filterFn) {
const elements = document.querySelectorAll(selector);
return Array.from(elements).filter(filterFn);
}
// Ejemplos de uso
const visibleParagraphs = selectElements('p', (el) => el.offsetWidth > 0 && el.offsetHeight > 0);
const elementsWithText = selectElements('div', (el) => el.textContent.trim().length > 0);
Manipulación de contenido
// Modificar contenido de texto
const heading = document.querySelector('h1');
// Diferentes formas de cambiar contenido
heading.textContent = 'Nuevo título'; // Solo texto, seguro contra XSS
heading.innerHTML = '<em>Título enfatizado</em>'; // HTML, cuidado con XSS
heading.innerText = 'Texto visible'; // Respeta CSS (display, visibility)
// Obtener vs establecer
console.log('Contenido:', heading.textContent);
console.log('HTML interno:', heading.innerHTML);
console.log('Texto visible:', heading.innerText);
// Manipulación segura de HTML
function safeSetHTML(element, htmlString) {
// Crear un contenedor temporal
const temp = document.createElement('div');
temp.innerHTML = htmlString;
// Limpiar scripts potencialmente maliciosos
const scripts = temp.querySelectorAll('script');
scripts.forEach((script) => script.remove());
// Establecer contenido limpio
element.innerHTML = temp.innerHTML;
}
// Trabajar con atributos
const input = document.querySelector('input[type="text"]');
// Atributos estándar
input.setAttribute('placeholder', 'Escribe algo...');
input.setAttribute('maxlength', '100');
const value = input.getAttribute('placeholder');
input.removeAttribute('disabled');
// Propiedades vs atributos
input.value = 'Valor en la propiedad'; // Propiedad (estado actual)
input.setAttribute('value', 'Valor inicial'); // Atributo (valor inicial)
// Data attributes
input.dataset.userId = '123'; // data-user-id="123"
input.dataset.lastModified = Date.now(); // data-last-modified="..."
console.log(input.dataset.userId); // Acceso a data attributes
// Clases CSS
const element = document.querySelector('.example');
// Manipulación de clases
element.classList.add('new-class');
element.classList.remove('old-class');
element.classList.toggle('active'); // Añade si no existe, quita si existe
element.classList.replace('old', 'new');
const hasClass = element.classList.contains('active');
// Múltiples clases
element.classList.add('class1', 'class2', 'class3');
element.classList.remove('class1', 'class2');
// Estilos inline
element.style.color = 'red';
element.style.backgroundColor = 'yellow';
element.style.fontSize = '18px';
// Establecer múltiples estilos
Object.assign(element.style, {
width: '200px',
height: '100px',
border: '1px solid black',
borderRadius: '5px',
});
// Obtener estilos computados
const computedStyle = window.getComputedStyle(element);
console.log('Color computado:', computedStyle.color);
console.log('Ancho computado:', computedStyle.width);
Creación y inserción de elementos
// Crear nuevos elementos
const newDiv = document.createElement('div');
const newText = document.createTextNode('Hola mundo');
const newComment = document.createComment('Este es un comentario');
// Configurar el nuevo elemento
newDiv.textContent = 'Contenido del div';
newDiv.className = 'new-element';
newDiv.id = 'dynamic-element';
// Métodos de inserción
const container = document.getElementById('container');
// Insertar al final
container.appendChild(newDiv);
// Insertar antes de un elemento específico
const referenceElement = container.querySelector('.intro');
container.insertBefore(newDiv, referenceElement);
// Métodos modernos de inserción (más flexibles)
const newElement = document.createElement('p');
newElement.textContent = 'Párrafo insertado';
// Insertar en posiciones específicas
container.insertAdjacentElement('beforebegin', newElement); // Antes del container
container.insertAdjacentElement('afterbegin', newElement); // Primer hijo
container.insertAdjacentElement('beforeend', newElement); // Último hijo
container.insertAdjacentElement('afterend', newElement); // Después del container
// Insertar HTML directamente
container.insertAdjacentHTML('beforeend', '<p>HTML insertado</p>');
// Crear elementos complejos de forma eficiente
function createElement(tag, attributes = {}, children = []) {
const element = document.createElement(tag);
// Establecer atributos
Object.entries(attributes).forEach(([key, value]) => {
if (key === 'textContent') {
element.textContent = value;
} else if (key === 'innerHTML') {
element.innerHTML = value;
} else if (key === 'dataset') {
Object.entries(value).forEach(([dataKey, dataValue]) => {
element.dataset[dataKey] = dataValue;
});
} else {
element.setAttribute(key, value);
}
});
// Añadir hijos
children.forEach((child) => {
if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
} else {
element.appendChild(child);
}
});
return element;
}
// Uso del helper
const complexElement = createElement(
'div',
{
class: 'card',
id: 'user-card',
dataset: { userId: '123', role: 'admin' },
},
[
createElement('h3', { textContent: 'Usuario Admin' }),
createElement('p', { textContent: 'Descripción del usuario...' }),
createElement('button', {
textContent: 'Editar',
class: 'btn btn-primary',
}),
]
);
document.body.appendChild(complexElement);
// Document Fragments para inserciones eficientes
const fragment = document.createDocumentFragment();
// Crear múltiples elementos
for (let i = 0; i < 1000; i++) {
const item = document.createElement('div');
item.textContent = `Item ${i}`;
fragment.appendChild(item); // No causa reflow
}
// Una sola inserción en el DOM (eficiente)
container.appendChild(fragment);
Eliminación y reemplazo de elementos
// Métodos de eliminación
const elementToRemove = document.querySelector('.to-remove');
// Eliminar elemento (método moderno)
elementToRemove.remove();
// Método clásico (eliminar desde el padre)
elementToRemove.parentNode.removeChild(elementToRemove);
// Eliminar todos los hijos
const parent = document.querySelector('.parent');
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
// Método más eficiente para limpiar contenido
parent.innerHTML = ''; // Rápido pero no ejecuta cleanup
parent.textContent = ''; // Solo para contenido de texto
// Reemplazo de elementos
const oldElement = document.querySelector('.old');
const newElement = document.createElement('div');
newElement.textContent = 'Elemento nuevo';
newElement.className = 'new';
// Reemplazar elemento
oldElement.parentNode.replaceChild(newElement, oldElement);
// Método moderno
oldElement.replaceWith(newElement);
// Función helper para reemplazo seguro
function replaceElement(oldElement, newElement) {
if (oldElement && oldElement.parentNode) {
oldElement.parentNode.replaceChild(newElement, oldElement);
return true;
}
return false;
}
// Clonación de elementos
const original = document.querySelector('.template');
const clone = original.cloneNode(true); // true = deep clone (incluye hijos)
const shallowClone = original.cloneNode(false); // solo el elemento
// Modificar clon antes de insertar
clone.id = 'cloned-element';
clone.querySelector('.title').textContent = 'Título clonado';
document.body.appendChild(clone);
Eventos del DOM: interactividad
// Event listeners básicos
const button = document.querySelector('#my-button');
// Agregar evento
button.addEventListener('click', function (event) {
console.log('Botón clickeado!');
console.log('Elemento que disparó el evento:', event.target);
console.log('Elemento que escucha el evento:', event.currentTarget);
});
// Diferentes tipos de eventos
const input = document.querySelector('#my-input');
input.addEventListener('focus', () => console.log('Input enfocado'));
input.addEventListener('blur', () => console.log('Input desenfocado'));
input.addEventListener('input', (e) => console.log('Valor:', e.target.value));
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
console.log('Enter presionado');
}
});
// Event delegation (delegación de eventos)
const list = document.querySelector('#dynamic-list');
// En lugar de agregar eventos a cada elemento
list.addEventListener('click', function (event) {
// Verificar si el elemento clickeado es un item de lista
if (event.target.matches('.list-item')) {
console.log('Item clickeado:', event.target.textContent);
}
// O usar closest para elementos anidados
const listItem = event.target.closest('.list-item');
if (listItem) {
console.log('Item o hijo clickeado:', listItem.textContent);
}
});
// Remover event listeners
function handleClick(event) {
console.log('Click manejado');
}
button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick); // Se requiere la misma función
// Eventos con opciones
button.addEventListener('click', handleClick, {
once: true, // Se ejecuta solo una vez
passive: true, // No llamará preventDefault()
capture: true, // Captura en fase de captura
});
// Prevenir comportamiento por defecto
const form = document.querySelector('#my-form');
form.addEventListener('submit', function (event) {
event.preventDefault(); // Previene envío del formulario
// Validación personalizada
const formData = new FormData(form);
console.log('Datos del formulario:', Object.fromEntries(formData));
});
// Custom events (eventos personalizados)
const customEvent = new CustomEvent('userAction', {
detail: {
userId: 123,
action: 'login',
timestamp: Date.now(),
},
bubbles: true,
cancelable: true,
});
// Disparar evento personalizado
document.dispatchEvent(customEvent);
// Escuchar evento personalizado
document.addEventListener('userAction', function (event) {
console.log('Acción del usuario:', event.detail);
});
Manipulación avanzada del DOM
// Observers para cambios en el DOM
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
console.log('Tipo de mutación:', mutation.type);
if (mutation.type === 'childList') {
console.log('Nodos añadidos:', mutation.addedNodes);
console.log('Nodos removidos:', mutation.removedNodes);
} else if (mutation.type === 'attributes') {
console.log('Atributo cambiado:', mutation.attributeName);
}
});
});
// Observar cambios
const targetNode = document.getElementById('observed-element');
observer.observe(targetNode, {
childList: true, // Observar cambios en hijos
subtree: true, // Observar todo el subárbol
attributes: true, // Observar cambios de atributos
attributeOldValue: true, // Guardar valor anterior
characterData: true, // Observar cambios en texto
});
// Detener observación
// observer.disconnect();
// Intersection Observer para elementos visibles
const intersectionObserver = new IntersectionObserver(
function (entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log('Elemento visible:', entry.target);
entry.target.classList.add('animate-in');
} else {
entry.target.classList.remove('animate-in');
}
});
},
{
threshold: 0.5, // 50% del elemento debe ser visible
rootMargin: '10px', // Margen adicional
}
);
// Observar elementos para lazy loading
document.querySelectorAll('.lazy-load').forEach((el) => {
intersectionObserver.observe(el);
});
// Resize Observer para cambios de tamaño
const resizeObserver = new ResizeObserver(function (entries) {
entries.forEach((entry) => {
console.log('Nuevo tamaño:', entry.contentRect.width, 'x', entry.contentRect.height);
// Ajustar layout basado en el tamaño
const element = entry.target;
if (entry.contentRect.width < 300) {
element.classList.add('compact');
} else {
element.classList.remove('compact');
}
});
});
resizeObserver.observe(document.querySelector('#responsive-element'));
Performance y optimización del DOM
// Batch DOM operations para mejor rendimiento
function optimizedDOMUpdates() {
const container = document.getElementById('container');
// ❌ Malo: múltiples reflows
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
container.appendChild(div); // Reflow en cada inserción
}
// ✅ Bueno: una sola inserción
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment); // Un solo reflow
}
// Debounce para eventos frecuentes
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Aplicar debounce a scroll/resize
window.addEventListener(
'scroll',
debounce(function () {
console.log('Scroll event processed');
}, 100)
);
// RequestAnimationFrame para animaciones suaves
function animateElement(element, duration = 1000) {
const start = performance.now();
const initialOpacity = 0;
const finalOpacity = 1;
function animate(currentTime) {
const elapsed = currentTime - start;
const progress = Math.min(elapsed / duration, 1);
// Interpolación lineal
const opacity = initialOpacity + (finalOpacity - initialOpacity) * progress;
element.style.opacity = opacity;
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
// Virtual DOM simple (concepto básico)
class VirtualDOM {
constructor() {
this.oldTree = null;
}
createElement(tag, props, ...children) {
return {
tag,
props: props || {},
children: children.flat(),
};
}
render(vdom, container) {
if (this.oldTree) {
this.updateElement(container, vdom, this.oldTree);
} else {
this.createDOMElement(vdom, container);
}
this.oldTree = vdom;
}
createDOMElement(vdom) {
if (typeof vdom === 'string' || typeof vdom === 'number') {
return document.createTextNode(vdom);
}
const element = document.createElement(vdom.tag);
Object.entries(vdom.props).forEach(([key, value]) => {
element.setAttribute(key, value);
});
vdom.children.forEach((child) => {
element.appendChild(this.createDOMElement(child));
});
return element;
}
updateElement(parent, newNode, oldNode, index = 0) {
// Implementación básica de diffing
if (!oldNode) {
parent.appendChild(this.createDOMElement(newNode));
} else if (!newNode) {
parent.removeChild(parent.childNodes[index]);
} else if (this.hasChanged(newNode, oldNode)) {
parent.replaceChild(this.createDOMElement(newNode), parent.childNodes[index]);
} else if (newNode.tag) {
const newLength = newNode.children.length;
const oldLength = oldNode.children.length;
for (let i = 0; i < Math.max(newLength, oldLength); i++) {
this.updateElement(parent.childNodes[index], newNode.children[i], oldNode.children[i], i);
}
}
}
hasChanged(node1, node2) {
return (
typeof node1 !== typeof node2 ||
(typeof node1 === 'string' && node1 !== node2) ||
node1.tag !== node2.tag
);
}
}
// Uso del Virtual DOM
const vdom = new VirtualDOM();
const app = document.getElementById('app');
// Renderizar componente inicial
vdom.render(
vdom.createElement(
'div',
{ class: 'container' },
vdom.createElement('h1', {}, 'Mi App'),
vdom.createElement('p', {}, 'Contenido dinámico')
),
app
);
Herramientas de debugging para DOM
// Utilidades de debugging
const DOMDebugger = {
// Encontrar todos los event listeners
getEventListeners(element) {
return getEventListeners(element); // Chrome DevTools
},
// Resaltar elemento
highlight(element, color = 'red') {
const originalOutline = element.style.outline;
element.style.outline = `2px solid ${color}`;
setTimeout(() => {
element.style.outline = originalOutline;
}, 2000);
},
// Información detallada del elemento
inspectElement(element) {
console.group('DOM Element Inspection');
console.log('Element:', element);
console.log('Tag Name:', element.tagName);
console.log('ID:', element.id);
console.log('Classes:', [...element.classList]);
console.log(
'Attributes:',
[...element.attributes].map((attr) => `${attr.name}="${attr.value}"`)
);
console.log('Computed Style:', window.getComputedStyle(element));
console.log('Parent:', element.parentElement);
console.log('Children:', [...element.children]);
console.log('Siblings:', this.getSiblings(element));
console.groupEnd();
},
getSiblings(element) {
return [...element.parentElement.children].filter((child) => child !== element);
},
// Monitorear cambios en elemento
watchElement(element, callback) {
const observer = new MutationObserver(callback);
observer.observe(element, {
attributes: true,
childList: true,
subtree: true,
characterData: true,
});
return observer;
},
// Medir performance de operaciones DOM
measureDOM(operation, description = 'DOM Operation') {
const start = performance.now();
operation();
const end = performance.now();
console.log(`${description} took ${end - start} milliseconds`);
},
};
// Ejemplos de uso
const element = document.querySelector('#test-element');
// Inspeccionar elemento
DOMDebugger.inspectElement(element);
// Resaltar elemento
DOMDebugger.highlight(element, 'blue');
// Monitorear cambios
const observer = DOMDebugger.watchElement(element, function (mutations) {
console.log('Element changed:', mutations);
});
// Medir rendimiento
DOMDebugger.measureDOM(() => {
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
document.body.appendChild(div);
document.body.removeChild(div);
}
}, 'Create/Remove 1000 elements');
Mejores prácticas y patrones comunes
// Patrón Module para encapsular funcionalidad DOM
const DOMUtils = (function () {
'use strict';
// Funciones privadas
function isElement(obj) {
return obj instanceof Element || obj instanceof HTMLDocument;
}
function sanitizeHTML(str) {
const temp = document.createElement('div');
temp.textContent = str;
return temp.innerHTML;
}
// API pública
return {
// Selección mejorada
$(selector, context = document) {
return context.querySelector(selector);
},
$$(selector, context = document) {
return Array.from(context.querySelectorAll(selector));
},
// Creación de elementos con configuración
create(tag, config = {}) {
const element = document.createElement(tag);
Object.entries(config).forEach(([key, value]) => {
switch (key) {
case 'text':
element.textContent = value;
break;
case 'html':
element.innerHTML = sanitizeHTML(value);
break;
case 'classes':
element.classList.add(...(Array.isArray(value) ? value : [value]));
break;
case 'data':
Object.entries(value).forEach(([dataKey, dataValue]) => {
element.dataset[dataKey] = dataValue;
});
break;
case 'events':
Object.entries(value).forEach(([event, handler]) => {
element.addEventListener(event, handler);
});
break;
default:
element.setAttribute(key, value);
}
});
return element;
},
// Manipulación de clases mejorada
toggleClasses(element, ...classes) {
classes.forEach((cls) => element.classList.toggle(cls));
},
// Event delegation helper
delegate(parent, selector, event, handler) {
parent.addEventListener(event, function (e) {
const target = e.target.closest(selector);
if (target) {
handler.call(target, e);
}
});
},
// Animaciones simples
fadeIn(element, duration = 300) {
element.style.opacity = '0';
element.style.display = 'block';
const start = performance.now();
function animate(timestamp) {
const progress = (timestamp - start) / duration;
element.style.opacity = Math.min(progress, 1);
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
},
fadeOut(element, duration = 300) {
const start = performance.now();
const initialOpacity = parseFloat(window.getComputedStyle(element).opacity);
function animate(timestamp) {
const progress = (timestamp - start) / duration;
element.style.opacity = initialOpacity * (1 - Math.min(progress, 1));
if (progress < 1) {
requestAnimationFrame(animate);
} else {
element.style.display = 'none';
}
}
requestAnimationFrame(animate);
},
};
})();
// Uso de DOMUtils
const container = DOMUtils.$('#container');
const newButton = DOMUtils.create('button', {
text: 'Click me!',
classes: ['btn', 'btn-primary'],
data: { action: 'submit', userId: '123' },
events: {
click: function (e) {
console.log('Button clicked!', this.dataset);
},
},
});
container.appendChild(newButton);
// Event delegation
DOMUtils.delegate(document.body, '.dynamic-button', 'click', function (e) {
console.log('Dynamic button clicked:', this);
});
Integración con frameworks modernos
// Interoperabilidad con React (cuando necesites DOM directo)
function useDirectDOM(callback, deps) {
const ref = useRef();
useEffect(() => {
if (ref.current) {
callback(ref.current);
}
}, deps);
return ref;
}
// Componente React que usa DOM directo
function ChartComponent({ data }) {
const chartRef = useDirectDOM(
(element) => {
// Limpiar chart anterior
element.innerHTML = '';
// Crear chart con librería que requiere DOM directo
const chart = new SomeChartLibrary(element);
chart.render(data);
// Cleanup
return () => chart.destroy();
},
[data]
);
return <div ref={chartRef} className="chart-container" />;
}
// Custom hooks para DOM
function useElementSize(ref) {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
if (!ref.current) return;
const resizeObserver = new ResizeObserver((entries) => {
const { width, height } = entries[0].contentRect;
setSize({ width, height });
});
resizeObserver.observe(ref.current);
return () => resizeObserver.disconnect();
}, [ref]);
return size;
}
function useIntersection(ref, options) {
const [isIntersecting, setIsIntersecting] = useState(false);
useEffect(() => {
if (!ref.current) return;
const observer = new IntersectionObserver(([entry]) => {
setIsIntersecting(entry.isIntersecting);
}, options);
observer.observe(ref.current);
return () => observer.disconnect();
}, [ref, options]);
return isIntersecting;
}
Conclusión
El DOM es el corazón de la interactividad web - es el puente que conecta tu HTML estático con la magia dinámica de JavaScript. Dominarlo significa tener el poder de crear experiencias web verdaderamente interactivas y responsivas.
Principios clave para el DOM efectivo:
- Selecciona eficientemente - Usa los selectores más específicos y rápidos posibles
- Minimiza los reflows - Agrupa las operaciones DOM para mejor rendimiento
- Usa event delegation - Un listener en el padre es mejor que cientos en los hijos
- Observa cambios inteligentemente - MutationObserver, IntersectionObserver, ResizeObserver
- Sanitiza siempre - Nunca confíes en contenido externo sin limpiar
El DOM moderno no es solo manipulación - es sobre observación, reactividad y performance. Con APIs como los Observers y requestAnimationFrame, puedes crear experiencias que rivalizan con aplicaciones nativas.
Recuerda: Cada cambio en el DOM tiene un costo. La diferencia entre un desarrollador novato y uno experto está en saber cuándo y cómo tocar el DOM. A veces la mejor manipulación del DOM es la que no haces.
El DOM es tu lienzo digital - úsalo sabiamente para pintar experiencias que deleiten a tus usuarios sin sacrificar performance.