Introducción
Hasta ahora tus programas escribían en la consola o dibujaban en un <canvas>. Ahora vas a hacer que JavaScript cambie la propia página: textos, clases, estilos y elementos nuevos. Para eso se usa el DOM.
Hasta ahora tus programas escribían en la consola o dibujaban en un <canvas>.
El DOM es el árbol de la página en memoria; document es la puerta de entrada.
querySelector (uno) y querySelectorAll (todos) con selectores de CSS.
textContent para textos; classList para cambiar el aspecto; setProperty para variables CSS.
data-* y dataset para datos propios; createElement y append para generar contenido a partir de datos.
Hasta ahora tus programas escribían en la consola o dibujaban en un <canvas>. Ahora vas a hacer que JavaScript cambie la propia página: textos, clases, estilos y elementos nuevos. Para eso se usa el DOM.
Cuando el navegador lee tu HTML, construye en memoria un árbol de objetos: el DOM (Document Object Model). Cada etiqueta es un nodo con padres e hijos:
document └── html ├── head │ └── title └── body ├── header │ └── h1 "Estudio Norte" └── main ├── p "Diseño gráfico e interactivo" └── ul ├── li "Branding" └── li "Web"
JavaScript accede a ese árbol con el objeto document. Si cambias el DOM, la página cambia al instante, sin recargar. Tu fichero index.html no se modifica: lo que cambia es la copia en memoria.
Puedes verlo en las DevTools, pestaña Elementos: lo que ves ahí es el DOM, no el HTML original.
Antes de cambiar algo hay que encontrarlo. Se usan los mismos selectores de CSS:
const titulo = document.querySelector("h1"); // el primero que coincida const menu = document.querySelector("#menu"); // por id const destacado = document.querySelector(".destacado"); // por clase const tarjetas = document.querySelectorAll(".tarjeta"); // todos: una lista
querySelector devuelve el primer elemento que coincide, o null si no hay ninguno. querySelectorAll devuelve todos en una lista (NodeList) que se recorre con forEach:
tarjetas.forEach((tarjeta) => { console.log(tarjeta.textContent); });
const titulo = document.querySelector("h1"); titulo.textContent = "Estudio Norte · Portfolio 2027";
textContent lee o cambia el texto. Es la opción segura: lo que escribas se muestra tal cual, aunque contenga < o >. innerHTML interpreta el texto como HTML. Úsalo solo con contenido que controlas tú, nunca con lo que escribe una persona en un formulario: podría colar código (inyección de HTML).
La mejor manera de cambiar el aspecto desde JavaScript es añadir o quitar clases y dejar el diseño en el CSS:
.oculto { display: none; } .activo { background: var(--color-marca); color: white; }
const aviso = document.querySelector(".aviso"); aviso.classList.add("oculto"); // añade la clase aviso.classList.remove("oculto"); // la quita aviso.classList.toggle("activo"); // la pone si no está; la quita si está aviso.classList.contains("activo"); // true o false
Así el diseño sigue en su sitio (el CSS) y JavaScript solo decide cuándo cambia.
Puedes cambiar un estilo concreto con style, pero úsalo para valores que se calculan (una posición, un color elegido por el usuario):
aviso.style.backgroundColor = "hsl(210 70% 45%)"; // propiedades en camelCase
Mejor aún: cambiar una variable CSS (lección El color en CSS). Todos los elementos que la usen se actualizan a la vez:
document.documentElement.style.setProperty("--color-marca", "#e63946");
const foto = document.querySelector(".foto"); foto.setAttribute("alt", "Cartel del festival en tonos rojos"); foto.getAttribute("src");
Los atributos data-* sirven para guardar datos propios en el HTML. Se leen con dataset:
<article class="tarjeta" data-categoria="branding" data-anio="2026">…</article>
const tarjeta = document.querySelector(".tarjeta"); tarjeta.dataset.categoria; // "branding" tarjeta.dataset.anio; // "2026" (siempre texto)
Muchas webs no escriben cada tarjeta a mano: las generan a partir de datos (un array de objetos, como en la lección Arrays y objetos). Se crea el elemento, se rellena y se añade:
const proyectos = [ { titulo: "Identidad Café Lento", categoria: "branding" }, { titulo: "Web del festival", categoria: "web" }, ]; const lista = document.querySelector("#proyectos"); proyectos.forEach((p) => { const item = document.createElement("li"); item.textContent = p.titulo; item.dataset.categoria = p.categoria; lista.append(item); });
document.createElement("li") crea el elemento (todavía fuera de la página). append lo añade al final del elemento padre. prepend lo añade al principio. elemento.remove() lo elimina.
Como hasta ahora, enlaza el script en el <head> con defer: se ejecuta cuando el HTML ya está completo y todos los elementos existen.
<script src="js/main.js" defer></script>
Si seleccionas un elemento antes de que exista, obtienes null y el error típico: Cannot read properties of null.
En una página con un h1, un párrafo y una lista vacía <ul id="colores"></ul>:
Cambia el texto del h1 con textContent. Añade al párrafo la clase destacado (defínela en el CSS). Con un array ["#264653", "#2a9d8f", "#e9c46a"], crea un li por color con el código como texto y ese color de fondo (style.backgroundColor). Cambia la variable --color-marca con setProperty y comprueba que todo lo que la usa cambia.
null al seleccionar: el selector está mal escrito (falta el . o el #) o el script se ejecuta antes que el HTML (falta defer). Tratar el resultado de querySelectorAll como un solo elemento: tarjetas.classList no existe; hay que recorrerlo con forEach. Usar innerHTML con textos que escribe el usuario. Cambiar muchos style uno a uno en lugar de una clase. Olvidar que dataset devuelve texto: Number(tarjeta.dataset.anio).
Código del tema: classList.toggle()
Hasta ahora tus programas escribían en la consola o dibujaban en un <canvas>. Ahora vas a hacer que JavaScript cambie la propia página: textos, clases, estilos y elementos nuevos. Para eso se usa el DOM.
Cuando el navegador lee tu HTML, construye en memoria un árbol de objetos: el DOM (Document Object Model). Cada etiqueta es un nodo con padres e hijos:
document
└── html
├── head
│ └── title
└── body
├── header
│ └── h1 "Estudio Norte"
└── main
├── p "Diseño gráfico e interactivo"
└── ul
├── li "Branding"
└── li "Web"JavaScript accede a ese árbol con el objeto document. Si cambias el DOM, la página cambia al instante, sin recargar. Tu fichero index.html no se modifica: lo que cambia es la copia en memoria.
Puedes verlo en las DevTools, pestaña Elementos: lo que ves ahí es el DOM, no el HTML original.
Antes de cambiar algo hay que encontrarlo. Se usan los mismos selectores de CSS:
const titulo = document.querySelector("h1"); // el primero que coincida
const menu = document.querySelector("#menu"); // por id
const destacado = document.querySelector(".destacado"); // por clase
const tarjetas = document.querySelectorAll(".tarjeta"); // todos: una listaquerySelector devuelve el primer elemento que coincide, o null si no hay ninguno.querySelectorAll devuelve todos en una lista (NodeList) que se recorre con forEach:tarjetas.forEach((tarjeta) => {
console.log(tarjeta.textContent);
});💡 Consejo. Guarda en constantes los elementos que vas a usar varias veces y selecciónalos al principio del fichero. El código queda más claro y el navegador no busca lo mismo una y otra vez.
const titulo = document.querySelector("h1");
titulo.textContent = "Estudio Norte · Portfolio 2027";textContent lee o cambia el texto. Es la opción segura: lo que escribas se muestra tal cual, aunque contenga < o >.innerHTML interpreta el texto como HTML. Úsalo solo con contenido que controlas tú, nunca con lo que escribe una persona en un formulario: podría colar código (inyección de HTML).La mejor manera de cambiar el aspecto desde JavaScript es añadir o quitar clases y dejar el diseño en el CSS:
.oculto { display: none; }
.activo { background: var(--color-marca); color: white; }const aviso = document.querySelector(".aviso");
aviso.classList.add("oculto"); // añade la clase
aviso.classList.remove("oculto"); // la quita
aviso.classList.toggle("activo"); // la pone si no está; la quita si está
aviso.classList.contains("activo"); // true o falseAsí el diseño sigue en su sitio (el CSS) y JavaScript solo decide cuándo cambia.
Puedes cambiar un estilo concreto con style, pero úsalo para valores que se calculan (una posición, un color elegido por el usuario):
aviso.style.backgroundColor = "hsl(210 70% 45%)"; // propiedades en camelCaseMejor aún: cambiar una variable CSS (lección El color en CSS). Todos los elementos que la usen se actualizan a la vez:
document.documentElement.style.setProperty("--color-marca", "#e63946");(document.documentElement es el elemento <html>, donde vive :root.)
const foto = document.querySelector(".foto");
foto.setAttribute("alt", "Cartel del festival en tonos rojos");
foto.getAttribute("src");Los atributos data-* sirven para guardar datos propios en el HTML. Se leen con dataset:
<article class="tarjeta" data-categoria="branding" data-anio="2026">…</article>const tarjeta = document.querySelector(".tarjeta");
tarjeta.dataset.categoria; // "branding"
tarjeta.dataset.anio; // "2026" (siempre texto)Sirven, por ejemplo, para filtrar proyectos por categoría (lección Eventos).
Muchas webs no escriben cada tarjeta a mano: las generan a partir de datos (un array de objetos, como en la lección Arrays y objetos). Se crea el elemento, se rellena y se añade:
const proyectos = [
{ titulo: "Identidad Café Lento", categoria: "branding" },
{ titulo: "Web del festival", categoria: "web" },
];
const lista = document.querySelector("#proyectos");
proyectos.forEach((p) => {
const item = document.createElement("li");
item.textContent = p.titulo;
item.dataset.categoria = p.categoria;
lista.append(item);
});document.createElement("li") crea el elemento (todavía fuera de la página).append lo añade al final del elemento padre. prepend lo añade al principio.elemento.remove() lo elimina.Como hasta ahora, enlaza el script en el <head> con defer: se ejecuta cuando el HTML ya está completo y todos los elementos existen.
<script src="js/main.js" defer></script> Si seleccionas un elemento antes de que exista, obtienes null y el error típico: Cannot read properties of null.
En una página con un h1, un párrafo y una lista vacía <ul id="colores"></ul>:
h1 con textContent.destacado (defínela en el CSS).["#264653", "#2a9d8f", "#e9c46a"], crea un li por color con el código como texto y ese color de fondo (style.backgroundColor).--color-marca con setProperty y comprueba que todo lo que la usa cambia.null al seleccionar: el selector está mal escrito (falta el . o el #) o el script se ejecuta antes que el HTML (falta defer).querySelectorAll como un solo elemento: tarjetas.classList no existe; hay que recorrerlo con forEach.innerHTML con textos que escribe el usuario.style uno a uno en lugar de una clase.dataset devuelve texto: Number(tarjeta.dataset.anio).