JavaScript DOM: Cómo cambiar el contenido de tu página web dinámicamente

Deja de hacer páginas estáticas y aburridas. Aprende a crear interactividad real con JavaScript DOM paso a paso

Llevas un rato haciendo páginas con HTML y CSS. Se ven bien. Pero cuando alguien hace click en algo... no pasa nada. La página es un cartel estático. Eso es exactamente lo que el DOM te permite cambiar.

El DOM es la parte de JavaScript que conecta tu código con lo que el usuario ve en pantalla. Aquí es donde todo el mundo se atasca al principio, porque nadie explica bien qué está pasando por debajo. Este tutorial lo hace diferente: cada concepto viene con un ejemplo que puedes probar ahora mismo.

¿Qué es el DOM (sin tecnicismos)?

El DOM es como una versión "viva" de tu HTML que JavaScript puede tocar, cambiar y manipular. Imagínate que tu página HTML es un documento de papel, pero el DOM es la versión digital que puedes editar en tiempo real.

📄

HTML estático

Tu código HTML original. Como un documento impreso que no se puede cambiar.

🔄

DOM dinámico

La versión "viva" que JavaScript puede modificar: cambiar texto, colores, mostrar/ocultar elementos.

⚡

Resultado

Páginas que reaccionan, cambian y responden a las acciones del usuario.

Los 3 poderes básicos del DOM

1

Encontrar elementos

Le dices a JavaScript: "busca el botón con id='mi-boton'"

document.querySelector('#mi-boton')
2

Cambiar contenido

Le dices: "cambia el texto de ese elemento por esto otro"

elemento.textContent = "Nuevo texto"
3

Reaccionar a eventos

Le dices: "cuando hagan click aquí, ejecuta esta función"

elemento.addEventListener('click', función)

De 0 a interactivo: ejemplos que funcionan

Vamos a crear ejemplos progresivos. Cada uno será más interesante que el anterior, y todos funcionan de verdad.

Nivel 1: Tu primer botón que hace algo

Empezamos con lo más básico: un botón que cambia su propio texto cuando le haces click.

🎮 Demo interactivo:

👆 Este botón cambiará cuando le hagas click

HTML necesario:

<button id="mi-boton">Haz click aquí</button>

El JavaScript:

// 1. Encuentra el botón
const miBoton = document.querySelector('#mi-boton');

// 2. Escucha cuando le hagan click
miBoton.addEventListener('click', function() {
    // 3. Cambia el texto
    miBoton.textContent = '¡Funcionó!';
});

🤓 ¿Qué está pasando?

  1. Línea 2: Buscamos el elemento con id "mi-boton" y lo guardamos en una variable
  2. Línea 5: Le decimos "cuando te hagan click, ejecuta esta función"
  3. Línea 7: Cambiamos el texto del botón

Nivel 2: Mostrar y ocultar contenido

Ahora algo más útil: un botón que hace aparecer y desaparecer contenido. Esto es súper común en páginas reales.

🎮 Demo interactivo:

🎉 ¡Este contenido estaba oculto! 🎉

👆 Este botón revela contenido oculto

HTML estructurado:

<button id="boton-secreto">Revelar secreto</button>
<div id="contenido-secreto" class="oculto">
    ¡Este contenido estaba oculto!
</div>

CSS para ocultar:

.oculto {
    display: none;
}

.visible {
    display: block;
}

JavaScript inteligente:

const boton = document.querySelector('#boton-secreto');
const contenido = document.querySelector('#contenido-secreto');

boton.addEventListener('click', function() {
    // Si está oculto, mostrarlo. Si está visible, ocultarlo
    if (contenido.classList.contains('oculto')) {
        contenido.classList.remove('oculto');
        contenido.classList.add('visible');
        boton.textContent = 'Ocultar secreto';
    } else {
        contenido.classList.add('oculto');
        contenido.classList.remove('visible');
        boton.textContent = 'Revelar secreto';
    }
});

Nivel 3: Lista interactiva

Ahora vamos con algo más avanzado: agregar elementos a una lista dinámicamente. Esto es la base de muchas aplicaciones web.

🎮 Demo interactivo:

    👆 Escribe algo y agrégalo a la lista

    HTML base:

    <input type="text" id="mi-input" placeholder="Escribe algo...">
    <button id="agregar-btn">Agregar</button>
    <ul id="mi-lista"></ul>

    JavaScript más potente:

    const input = document.querySelector('#mi-input');
    const boton = document.querySelector('#agregar-btn');
    const lista = document.querySelector('#mi-lista');
    
    boton.addEventListener('click', function() {
        // Obtener el texto del input
        const texto = input.value;
        
        // Solo agregar si hay algo escrito
        if (texto !== '') {
            // Crear un nuevo elemento li
            const nuevoItem = document.createElement('li');
            nuevoItem.textContent = texto;
            
            // Agregarlo a la lista
            lista.appendChild(nuevoItem);
            
            // Limpiar el input
            input.value = '';
        }
    });

    Nivel 4: Contador interactivo

    Algo que parece "de aplicación real": un contador que sube y baja.

    🎮 Demo interactivo:

    0

    👆 Un contador funcional con botones

    HTML limpio:

    <div class="contador">
        <button id="restar">-</button>
        <span id="numero">0</span>
        <button id="sumar">+</button>
    </div>

    JavaScript profesional:

    Contador con múltiples event listeners
    // Variables para los elementos
    const botonRestar = document.querySelector('#restar');
    const botonSumar = document.querySelector('#sumar');
    const numeroDisplay = document.querySelector('#numero');
    
    // Variable para llevar la cuenta
    let contador = 0;
    
    // Función para sumar
    botonSumar.addEventListener('click', function() {
        contador++;
        numeroDisplay.textContent = contador;
    });
    
    // Función para restar
    botonRestar.addEventListener('click', function() {
        contador--;
        numeroDisplay.textContent = contador;
    });

    Los errores que comete todo el mundo al empezar con el DOM

    ❌

    Error #1: Script en el lugar incorrecto

    ❌ Malo:
    Script sin defer - Práctica incorrecta
    <head>
        <script src="script.js"></script>
    </head>
    ✅ Bueno:
    Script con defer - Práctica correcta
    <head>
        <script src="script.js" defer></script>
    </head>

    Por qué: Sin defer, JavaScript se ejecuta antes de que el HTML termine de cargar. Este es el error número uno de principiantes — el código parece correcto pero falla siempre, y la consola no da ningún error claro.

    ❌

    Error #2: Usar innerHTML sin control

    ❌ Malo:
    Uso inseguro de innerHTML
    elemento.innerHTML = userInput;
    ✅ Bueno:
    Uso seguro con textContent
    elemento.textContent = userInput;

    Por qué: `innerHTML` puede ejecutar código malicioso. `textContent` es más seguro.

    ❌

    Error #3: IDs que no existen

    ❌ Problema:
    Elemento inexistente causa error
    const boton = document.querySelector('#boton-que-no-existe');
    boton.addEventListener('click', ...); // Error!
    ✅ Solución:
    Verificación de elemento antes de usarlo
    const boton = document.querySelector('#mi-boton');
    if (boton) {
        boton.addEventListener('click', ...);
    }

    Por qué: Si el elemento no existe, el código se rompe. Siempre verifica primero.

    ❌

    Error #4: onclick en HTML

    ❌ Malo:
    Evento onclick directo en HTML
    <button onclick="miFuncion()">Click</button>
    ✅ Bueno:
    Separación de HTML y JavaScript
    // HTML
    <button id="mi-boton">Click</button>
    
    // JavaScript
    document.querySelector('#mi-boton')
        .addEventListener('click', miFuncion);

    Por qué: Separar HTML y JavaScript es más limpio y profesional.

    ❌

    Error #5: No usar nombres descriptivos

    ❌ Malo:
    Variables con nombres poco descriptivos
    const x = document.querySelector('#btn');
    const y = 0;
    ✅ Bueno:
    Variables con nombres descriptivos
    const botonContador = document.querySelector('#contador-btn');
    let numeroActual = 0;

    Por qué: En 1 semana no recordarás qué hace "x" o "y".

    Seleccionar elementos: las 3 formas que verás en todo el código

    Hasta aquí has usado querySelector para todo. Pero cuando leas código de otros — tutoriales de YouTube, proyectos de GitHub, código de trabajo — vas a encontrar otras dos variantes. Aquí están las tres juntas para que sepas qué hace cada una:

    // getElementById — el más antiguo, solo busca por id
    const boton = document.getElementById('mi-boton');
    // ⚠️ Sin el # — aquí el id va sin símbolo
    
    // querySelector — selector CSS, devuelve el primero que coincida
    const boton = document.querySelector('#mi-boton');
    // Con el # para ids, con . para clases, sin nada para etiquetas
    
    // querySelectorAll — devuelve TODOS los que coincidan
    const botones = document.querySelectorAll('.boton-card');
    // Devuelve una NodeList — iterar con forEach
    botones.forEach(function(boton) {
        boton.addEventListener('click', miFuncion);
    });

    El error clásico con querySelectorAll:

    querySelectorAll devuelve una NodeList, no un array. La mayoría de métodos de array como map o filter no funcionan directamente. forEach sí funciona. Si necesitas array de verdad: Array.from(document.querySelectorAll('.clase')).

    ¿Cuándo usar cada una? En código nuevo usa querySelector y querySelectorAll siempre — son más flexibles. getElementById lo verás en código antiguo o en tutoriales de hace 5 años y hace exactamente lo mismo para ids.

    Cambiar atributos con setAttribute

    Los atributos HTML son cosas como src de una imagen, href de un enlace, disabled de un botón, o cualquier data-* que pongas tú. JavaScript puede leerlos y cambiarlos en tiempo real.

    // Leer un atributo
    const url = enlace.getAttribute('href');
    
    // Cambiar un atributo
    imagen.setAttribute('src', 'nueva-foto.jpg');
    enlace.setAttribute('href', 'https://otro-sitio.com');
    
    // Deshabilitar / habilitar un botón
    boton.setAttribute('disabled', '');    // deshabilita
    boton.removeAttribute('disabled');       // vuelve a habilitarlo
    
    // Para propiedades booleanas hay un atajo más directo
    boton.disabled = true;
    input.required = true;

    Demo — deshabilitar y habilitar un botón:

    Estado: habilitado

    Modificar estilos con .style

    Ya sabes que classList es la forma correcta de cambiar estilos: añades o quitas una clase y el CSS hace el resto. Pero a veces necesitas un valor calculado en tiempo de ejecución — una posición basada en un click, un tamaño que depende de datos del servidor. Para eso existe .style.

    // Los nombres de propiedad van en camelCase, no kebab-case
    elemento.style.color = 'red';
    elemento.style.backgroundColor = '#f0f0f0';  // background-color → backgroundColor
    elemento.style.fontSize = '24px';           // font-size → fontSize
    elemento.style.display = 'none';
    
    // Para quitar un estilo inline, asigna string vacío
    elemento.style.color = '';

    Cuándo NO usar .style:

    Si el estado es fijo (activo / inactivo, visible / oculto), usa classList. Los estilos inline tienen la máxima especificidad CSS y son difíciles de sobrescribir desde tu hoja de estilos. .style tiene sentido cuando el valor es dinámico: la posición exacta de un tooltip, el ancho de una barra de progreso al 73%, el color calculado a partir de un dato.

    Un ejemplo real de esto último es cambiar de tema con JavaScript y el DOM: como el estado es fijo (claro o oscuro), lo correcto es alternar una clase en el body con classList.toggle, no usar .style a mano.

    Más eventos que el click

    El 80% de los tutoriales solo usan click. En proyectos reales hay otros tres que vas a necesitar sí o sí:

    input — reaccionar mientras el usuario escribe

    const campo = document.querySelector('#email');
    const error = document.querySelector('#error-msg');
    
    campo.addEventListener('input', function() {
        if (campo.value.includes('@')) {
            error.textContent = '';
        } else {
            error.textContent = 'Necesita una @';
        }
    });

    submit — interceptar formularios

    Sin preventDefault(), el formulario recarga la página. Esto es lo primero que tienes que aprender si trabajas con formularios y JavaScript:

    const formulario = document.querySelector('#mi-formulario');
    
    formulario.addEventListener('submit', function(event) {
        event.preventDefault();  // ← evita el reload
    
        // Ahora puedes leer los datos y hacer lo que quieras
        const email = document.querySelector('#email').value;
        console.log('Email enviado:', email);
    });

    keydown — detectar teclas concretas

    document.addEventListener('keydown', function(event) {
        if (event.key === 'Escape') {
            cerrarModal();
        }
        if (event.key === 'Enter') {
            confirmar();
        }
    });

    Event delegation: un listener para gobernarlos a todos

    Este es el concepto que separa a quien entiende el DOM de quien lo usa sin entenderlo. Cuando tienes una lista de elementos y quieres que cada uno reaccione a un click, el instinto es poner un listener en cada uno. El problema: si los elementos se crean dinámicamente (como en el demo3), los listeners que pusiste antes no existen en los nuevos.

    La solución es poner el listener en el elemento padre y dejar que el evento suba:

    // ❌ Esto falla con elementos creados dinámicamente
    document.querySelectorAll('li').forEach(function(li) {
        li.addEventListener('click', eliminar);
        // Los li que crees DESPUÉS no tienen este listener
    });
    
    // ✅ Event delegation: el listener está en el padre
    const lista = document.querySelector('#mi-lista');
    
    lista.addEventListener('click', function(event) {
        // event.target es el elemento exacto donde hicieron click
        if (event.target.tagName === 'LI') {
            event.target.remove();
        }
    });
    // Ahora funciona con todos los li, incluso los que añadas después

    Aquí event.target es el elemento donde cayó el click. El evento sube por el árbol DOM (se llama bubbling) hasta llegar al padre donde tienes el listener. Eso es todo.

    Demo — lista con eliminación por event delegation:

      Haz click en el ✕ de cada item para eliminarlo — un solo listener en la lista hace todo

      Proyecto completo: Todo junto

      Aquí tienes un proyecto que combina todo lo anterior. Mira el código fuente cuando lo veas funcionar — es más fácil entenderlo cuando ya has pasado por los niveles anteriores:

      🎮 Demo final - Mini aplicación:

      Mi Primera Aplicación DOM

        Items en la lista: 0

        👆 Una mini-aplicación que combina todo lo que aprendiste

        🏆 Desafío para ti:

        ¿Puedes crear tu propia versión de esta aplicación? Intenta agregar estas funciones:

        • ✅ Botón para eliminar elementos individuales
        • ✅ Cambiar el color de fondo cuando agregues algo
        • ✅ Mostrar un mensaje cuando la lista esté vacía
        • ✅ Contar cuántas palabras has agregado en total

        ¿Qué sigue después del DOM?

        Con esto ya puedes hacer que una página reaccione al usuario. El siguiente paso lógico es conectar esa página con datos reales que vienen de internet — y para eso existe la Fetch API.

        🌐

        APIs y Fetch

        Conecta tu página con servicios externos. Trae datos de internet en tiempo real.

        📱

        Proyectos reales

        Lista de tareas, calculadora, juegos simples. Proyectos que puedes mostrar.

        ⚛️

        Frameworks

        React, Vue... pero solo después de dominar JavaScript puro.

        🎯 Tu siguiente paso

        Dominas lo básico del DOM. Ahora aprende a conectar tu página con servicios reales.

        👉 APIs para principiantes: Tu primera llamada fetch() con JavaScript

        Aprende a traer datos de internet: clima, noticias, usuarios. El siguiente nivel de interactividad.

        Una cosa antes de seguir:

        Cuando algo no funcione, abre la consola del navegador (F12 → Console). El 90% de los errores con el DOM aparecen ahí con el mensaje exacto. Cannot read properties of null casi siempre significa que el elemento no existe o el script se ejecutó antes de que el HTML cargara.