Mapas de imagen, iframes y divisores de contenido
Mapas de imagen, iframes y divisores de contenido
En el capitulo 6 desarmamos el enlace: vimos que una URL es una direccion con partes bien definidas, que las rutas relativas se resuelven contra el documento actual, y que target decide en que contexto se abre el destino. Todo eso tenia un punto en comun: el area que el usuario podia pulsar era el texto o la imagen completa envuelta por un <a>.
Este capitulo rompe ese supuesto en tres direcciones distintas.
Primero, vamos a partir una sola imagen en varias zonas pulsables independientes usando <map> y <area>: un mapa de imagen. Segundo, vamos a incrustar una pagina completa dentro de otra con <iframe>, lo que nos obliga a hablar de contextos de navegacion anidados y, sobre todo, de seguridad. Y tercero, vamos a ordenar el documento con los contenedores que le dan forma: <hr>, <div>, <span>, <header>, <nav>, <main>, <section>, <article>, <aside> y <footer>.
Los tres temas parecen inconexos, pero comparten una idea: el navegador no solo dibuja pixeles, tambien construye un mapa de regiones interactivas y una estructura de significado. Los mapas de imagen definen regiones dentro de una imagen; los iframes definen regiones que son documentos completos; los divisores definen regiones de significado dentro del propio documento.
Que vas a poder hacer al terminar
- Convertir una ilustracion, un plano o un diagrama en un panel de enlaces sin recortar la imagen en pedazos.
- Calcular e interpretar las coordenadas de un
<area>para las cuatro formas disponibles. - Reconocer las limitaciones de los mapas de imagen y aplicar la alternativa moderna basada en CSS o SVG.
- Incrustar un video, un mapa o un documento externo con
<iframe>y entender que se ejecuta en un contexto separado. - Restringir lo que un contenido incrustado puede hacer usando
sandbox,allowyreferrerpolicy. - Impedir que tu propio sitio sea incrustado por terceros.
- Elegir el contenedor correcto entre
div,section,articley compania, sin caer en la sopa dediv.
Parte 1: mapas de imagen
El problema que resuelven
Imagina una imagen de un tablero de juego con cuatro personajes. Quieres que al pulsar cada personaje el usuario vaya a una pagina distinta. Con lo que sabemos hasta ahora hay dos caminos malos:
- Recortar la imagen en cuatro archivos y envolver cada uno en un
<a>. Funciona solo si las zonas son rectangulos alineados en una grilla, y multiplica las peticiones al servidor. - Poner una sola imagen dentro de un
<a>. Entonces toda la imagen apunta a un unico destino.
Un mapa de imagen (image map) resuelve el caso real: una sola imagen, varias zonas pulsables, cada una con su propia forma y su propio destino.
Anatomia de un mapa de imagen
Un mapa de imagen siempre son tres piezas trabajando juntas:
| Pieza | Etiqueta | Rol |
|---|---|---|
| La imagen | <img usemap="#nombre"> | Muestra los pixeles y declara que usa un mapa |
| El mapa | <map name="nombre"> | Contenedor con nombre que agrupa las zonas |
| Las zonas | <area> | Cada region pulsable, con forma, coordenadas y destino |
El vinculo entre la imagen y el mapa es puramente por nombre: el atributo usemap de la imagen contiene una almohadilla seguida del valor exacto del atributo name del <map>. Es el mismo mecanismo de fragmento que vimos en el capitulo anterior con los anclajes internos, pero aqui no navega a ninguna parte: solo identifica el mapa.
flowchart TD
A["Usuario hace clic en (x, y)<br/>sobre la imagen"] --> B{"La img tiene<br/>usemap?"}
B -- No --> C["Comportamiento normal:<br/>si esta dentro de un a, navega;<br/>si no, no pasa nada"]
B -- "Si: usemap='#mariokart'" --> D["Busca en el documento<br/>map name='mariokart'"]
D --> E{"Existe ese map?"}
E -- No --> C
E -- Si --> F["Recorre los area<br/>en orden de aparicion"]
F --> G{"El punto (x, y) cae<br/>dentro de esta area?"}
G -- No --> H{"Quedan mas area?"}
H -- Si --> F
H -- No --> I["Ninguna zona coincide:<br/>clic ignorado"]
G -- Si --> J{"El area tiene href?"}
J -- Si --> K["Navega al href<br/>respetando target"]
J -- No --> L["Zona muerta:<br/>bloquea las de abajo<br/>y no navega"]
Lo importante del diagrama: el navegador recorre las areas en el orden en que estan escritas y se queda con la primera que contenga el punto. El orden importa, y mucho.
El sistema de coordenadas
Las coordenadas de un <area> se expresan en pixeles y se miden desde la esquina superior izquierda de la imagen. El eje X crece hacia la derecha y el eje Y crece hacia abajo. Esto ultimo suele confundir a quien viene de matematicas: en la pantalla el eje Y esta invertido respecto al plano cartesiano habitual.
graph LR
subgraph IMG["Imagen de 800 x 600 px"]
direction TB
O["(0,0) esquina superior izquierda"]
X["(800,0) esquina superior derecha"]
Y["(0,600) esquina inferior izquierda"]
Z["(800,600) esquina inferior derecha"]
O -->|"X crece -->"| X
O -->|"Y crece hacia abajo"| Y
X --> Z
Y --> Z
end
Para obtener coordenadas reales tienes tres opciones practicas:
- Un editor de imagenes como GIMP, Krita o Photoshop: casi todos muestran la posicion del cursor en pixeles en una barra de estado.
- Las herramientas de desarrollo del navegador: abre la imagen sola en una pestana y usa la extension de regla, o mide con el inspector.
- JavaScript, que es lo mas comodo cuando estas afinando. Este fragmento imprime en consola la coordenada exacta de cada clic sobre la imagen:
<h1>Haz clic sobre la imagen para leer sus coordenadas</h1>
<p id="salida">Todavia no has hecho clic.</p>
<img id="lienzo" src="tablero.png" alt="Tablero del juego">
<script>
const imagen = document.getElementById('lienzo');
const salida = document.getElementById('salida');
imagen.addEventListener('click', function (evento) {
// Rectangulo que ocupa la imagen en la pantalla.
const caja = imagen.getBoundingClientRect();
// Coordenada del clic relativa a la esquina superior izquierda.
const xVisible = evento.clientX - caja.left;
const yVisible = evento.clientY - caja.top;
// Conversion a pixeles reales del archivo, por si la imagen
// se esta mostrando escalada.
const escalaX = imagen.naturalWidth / caja.width;
const escalaY = imagen.naturalHeight / caja.height;
const xReal = Math.round(xVisible * escalaX);
const yReal = Math.round(yVisible * escalaY);
salida.textContent =
'Visible: ' + Math.round(xVisible) + ',' + Math.round(yVisible) +
' | Real en el archivo: ' + xReal + ',' + yReal;
console.log(xReal + ',' + yReal);
});
</script>
Fijate en la distincion entre naturalWidth (el ancho real del archivo) y getBoundingClientRect().width (el ancho con el que se esta pintando). Volveremos a ella cuando hablemos de las limitaciones.
Las cuatro formas de <area>
El atributo shape acepta cuatro valores, y cada uno interpreta coords de forma distinta.
shape | Numero de valores en coords | Significado de los valores | Uso tipico |
|---|---|---|---|
rect | 4 | x1,y1,x2,y2: esquina superior izquierda y esquina inferior derecha | Botones, cuadros, celdas |
circle | 3 | x,y,r: centro y radio | Caras, iconos redondos, planetas |
poly | 6 o mas (pares) | x1,y1,x2,y2,x3,y3,...: vertices en orden | Paises, piezas irregulares, siluetas |
default | ninguno | Toda el area de la imagen no cubierta por las anteriores | Zona de respaldo |
Un detalle que se olvida siempre: poly necesita al menos tres pares de coordenadas, o sea seis numeros, porque con menos no hay poligono. El navegador cierra el poligono automaticamente uniendo el ultimo vertice con el primero: no repitas el punto inicial al final.
Veamos los cuatro casos en un ejemplo completo. Este documento funciona tal cual si colocas junto a el una imagen llamada mapa-mundo.png:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Las cuatro formas de area</title>
</head>
<body>
<h1>Panel de tecnologias</h1>
<map name="panel-tecnologias">
<area
shape="rect"
coords="93,178,385,457"
href="https://developer.mozilla.org/es/docs/Web/JavaScript"
target="_blank"
rel="noopener"
alt="Documentacion de JavaScript">
<area
shape="circle"
coords="199,611,75"
href="https://developer.mozilla.org/es/docs/Web/CSS"
target="_blank"
rel="noopener"
alt="Documentacion de CSS">
<area
shape="poly"
coords="520,140,700,220,660,420,470,380"
href="https://developer.mozilla.org/es/docs/Web/HTML"
target="_blank"
rel="noopener"
alt="Documentacion de HTML">
<area
shape="default"
href="/tecnologias/superclubnet/00-indice/"
alt="Volver al indice del curso">
</map>
<img
src="mapa-mundo.png"
usemap="#panel-tecnologias"
width="800"
height="700"
alt="Panel con tres zonas: JavaScript, CSS y HTML">
</body>
</html>
Tres observaciones sobre este codigo:
- El
<area>conshape="default"va al final. Como el navegador se queda con la primera coincidencia ydefaultcubre toda la imagen, ponerlo arriba haria que ninguna otra zona funcionara jamas. Es el error clasico. - Cada
<area>conhreflleva su propioalt. No es opcional: es el texto que lee un lector de pantalla y el equivalente del texto de un enlace normal. Un<area>conhrefy sinaltes HTML invalido. - El
rel="noopener"acompana atarget="_blank"por la misma razon que vimos en el capitulo anterior: evita que la pagina destino pueda manipular la ventana de origen a traves dewindow.opener. Los navegadores modernos ya lo aplican por defecto, pero declararlo es gratis y explicito.
Zonas muertas: cuando no quieres un enlace
A veces necesitas que una region no haga nada, pero que tampoco caiga en la zona default. Historicamente esto se hacia con un atributo nohref, que aparece en material antiguo. Ese atributo fue eliminado del estandar: hoy la forma correcta es simplemente omitir href.
<map name="tablero">
<!-- El centro del tablero no lleva a ninguna parte,
pero bloquea que el clic caiga en la zona default. -->
<area shape="circle" coords="400,350,60" alt="">
<area shape="rect" coords="0,0,200,200" href="/esquina-a/" alt="Esquina A">
<area shape="default" href="/ayuda/" alt="Ayuda general del tablero">
</map>
Un <area> sin href no es un enlace, no recibe foco de teclado y su alt debe quedar vacio. Sirve exclusivamente como escudo: intercepta el clic para que no lo tome una zona posterior.
Atributos completos de <area>
Como <area> es, conceptualmente, un <a> con forma geometrica, comparte casi todos sus atributos.
| Atributo | Para que sirve |
|---|---|
shape | Forma de la zona: rect, circle, poly o default |
coords | Coordenadas en pixeles, separadas por comas |
href | Destino del enlace. Si falta, la zona no es un enlace |
alt | Texto alternativo. Obligatorio cuando hay href |
target | Contexto de navegacion: _self, _blank, _parent, _top o el name de un iframe |
rel | Relacion con el destino: noopener, noreferrer, nofollow, etc. |
download | Descarga el recurso en vez de navegar hacia el |
ping | Lista de URL a las que notificar el clic |
referrerpolicy | Cuanta informacion de origen se envia al destino |
Es exactamente el vocabulario del capitulo 6 aplicado a una geometria. Si entendiste <a>, entendiste <area>.
La gran limitacion: los mapas no escalan
Las coordenadas de un <area> son numeros fijos en pixeles. Si muestras la imagen a su tamano original todo cuadra. Pero en cuanto haces esto:
img {
max-width: 100%;
height: auto;
}
la imagen se encoge en pantallas angostas y, en la practica, los navegadores no reescalan las coordenadas del mapa. El resultado es que las zonas pulsables quedan desplazadas respecto a lo que el usuario ve. En un movil, pulsar el personaje de la izquierda puede activar el enlace de otro.
Esta es la razon por la que los mapas de imagen clasicos se usan poco en sitios modernos. Existen tres salidas.
Salida 1: fijar el tamano de la imagen. Sirve solo si controlas el contexto (una pantalla de kiosco, un panel interno).
Salida 2: recalcular las coordenadas con JavaScript. Guardas las coordenadas originales y las multiplicas por el factor de escala cada vez que cambia el tamano:
<map name="zonas">
<area shape="rect" coords="93,178,385,457" href="/js/" alt="JavaScript">
<area shape="circle" coords="199,611,75" href="/css/" alt="CSS">
</map>
<img id="tablero" src="tablero.png" usemap="#zonas"
style="max-width:100%;height:auto;display:block"
alt="Tablero de tecnologias">
<script>
const imagen = document.getElementById('tablero');
const areas = document.querySelectorAll('map[name="zonas"] area');
// Guardamos las coordenadas originales una sola vez.
const originales = new Map();
areas.forEach(function (area) {
originales.set(area, area.coords.split(',').map(Number));
});
function reescalar() {
if (!imagen.naturalWidth) return;
const factor = imagen.clientWidth / imagen.naturalWidth;
areas.forEach(function (area) {
const base = originales.get(area);
const nuevas = base.map(function (valor) {
return Math.round(valor * factor);
});
area.coords = nuevas.join(',');
});
}
// Recalculamos al cargar la imagen y cada vez que cambie de tamano.
imagen.addEventListener('load', reescalar);
if (imagen.complete) reescalar();
new ResizeObserver(reescalar).observe(imagen);
</script>
ResizeObserver es una API real del navegador que avisa cuando un elemento cambia de tamano, sea por el viewport, por un contenedor flexible o por lo que sea. Es mas fiable que escuchar el evento resize de la ventana.
Un matiz importante: este reescalado funciona bien para rect y poly, y para circle solo si el factor horizontal y el vertical son iguales, que es lo normal cuando usas height: auto. Si deformas la imagen con proporciones distintas, un circulo deberia convertirse en elipse y <area> no sabe hacer eso.
Salida 3: no usar mapa de imagen. Es la opcion mas robusta. Colocas la imagen como fondo o como <img> dentro de un contenedor con position: relative y superpones enlaces normales posicionados en porcentajes. Al ser porcentajes, escalan solos:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zonas pulsables sin map</title>
<style>
.escenario {
position: relative;
max-width: 800px;
margin: 0 auto;
}
.escenario img {
display: block;
width: 100%;
height: auto;
}
.zona {
position: absolute;
display: block;
border: 2px solid transparent;
border-radius: 4px;
}
.zona:hover,
.zona:focus-visible {
border-color: #ff4081;
background: rgba(255, 64, 129, 0.15);
outline: none;
}
/* Coordenadas expresadas como porcentaje del contenedor:
escalan automaticamente con la imagen. */
.zona-js {
left: 11.6%;
top: 25.4%;
width: 36.5%;
height: 39.9%;
}
.zona-css {
left: 15.5%;
top: 76.6%;
width: 18.8%;
height: 21.4%;
border-radius: 50%;
}
</style>
</head>
<body>
<div class="escenario">
<img src="tablero.png" alt="Tablero con dos zonas interactivas">
<a class="zona zona-js" href="/tecnologias/superclubnet/08-javascript-tipos-variables-y-strings/">
<span class="visually-hidden">JavaScript</span>
</a>
<a class="zona zona-css" href="/tecnologias/superclubnet/00-indice/">
<span class="visually-hidden">CSS</span>
</a>
</div>
</body>
</html>
Para convertir un pixel en porcentaje: divide la coordenada por el ancho (o alto) real de la imagen y multiplica por cien. Con una imagen de 800 x 700, la coordenada X de 93 px equivale a 93 / 800 * 100 = 11.6%.
Esta version tiene tres ventajas sobre el mapa clasico: escala sola, permite estados visuales de hover y foco (algo que <area> no ofrece de forma comoda), y usa enlaces normales que cualquier herramienta entiende.
Si necesitas formas irregulares con esta tecnica, la herramienta correcta es un <svg> inline con elementos <path> envueltos en <a>, porque el SVG escala por definicion.
Comparacion rapida de tecnicas
| Tecnica | Formas irregulares | Escala sola | Estados hover/foco | Complejidad |
|---|---|---|---|---|
map + area clasico | Si (poly) | No | Muy limitados | Baja |
map + area + JavaScript | Si | Si | Muy limitados | Media |
| Enlaces absolutos en porcentajes | Solo rectangulos y elipses | Si | Completos | Baja |
SVG inline con <a> y <path> | Si | Si | Completos | Media |
Parte 2: la etiqueta <iframe>
De los frames a los iframes
En los primeros anos de la web existian <frameset> y <frame>: se partia la ventana del navegador en varias zonas y cada una cargaba un documento distinto. El patron tipico era un menu fijo a la izquierda y el contenido a la derecha. Funcionaba, pero traia problemas serios: la URL de la barra de direcciones no reflejaba lo que se estaba viendo, no se podia guardar un marcador util, el boton de atras se comportaba de forma erratica y los buscadores indexaban fragmentos sueltos sin contexto.
Ambas etiquetas quedaron obsoletas. Lo que sobrevivio fue <iframe>, el inline frame: en vez de partir la ventana, inserta un documento completo dentro del flujo de la pagina, como si fuera una imagen mas.
Que es realmente un iframe
Un <iframe> no es un contenedor de HTML. Es una ventana del navegador en miniatura: crea un contexto de navegacion anidado con su propio documento, su propio DOM, su propio historial, sus propios estilos y su propio JavaScript.
flowchart TB
subgraph VENTANA["Ventana del navegador"]
subgraph PADRE["Documento padre: tusitio.cl"]
H["head, body, DOM propio"]
CSSP["CSS propio"]
JSP["JavaScript propio<br/>window.top"]
IF["elemento iframe<br/>(una caja en el layout)"]
end
end
IF --> HIJO
subgraph HIJO["Contexto anidado: youtube.com/embed/..."]
H2["head, body, DOM independiente"]
CSS2["CSS independiente"]
JS2["JavaScript independiente<br/>window.parent apunta al padre"]
HIST["Historial de navegacion propio"]
end
JSP -. "bloqueado por<br/>politica de mismo origen" .-x JS2
JSP -. "solo mensajes<br/>via postMessage" .-> JS2
De ese diagrama salen las tres consecuencias practicas mas importantes:
- El CSS de tu pagina no entra al iframe. Si el contenido incrustado se ve feo, no puedes arreglarlo con tus hojas de estilo. Solo controlas el tamano y el borde de la caja exterior.
- El JavaScript esta aislado si los origenes son distintos. La politica de mismo origen impide que tu codigo lea el DOM del iframe y viceversa. La unica via de comunicacion es
postMessage. - Cada iframe descarga un documento completo. Con su HTML, su CSS, sus fuentes, sus imagenes y sus scripts. Cinco iframes son, en costo, cinco paginas cargando a la vez.
El ejemplo canonico: incrustar un video
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Video incrustado</title>
</head>
<body>
<h1>Repaso del capitulo</h1>
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/LeYLP5uerGQ"
title="Video explicativo del capitulo 7"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen>
</iframe>
</body>
</html>
Repasemos atributo por atributo, porque cada uno hace algo concreto:
| Atributo | Que hace |
|---|---|
src | URL del documento a cargar. Puede ser absoluta o relativa, con las reglas del capitulo 6 |
srcdoc | HTML en linea que se carga en lugar de src. Tiene prioridad sobre src |
title | Descripcion del contenido incrustado. Es lo que anuncia un lector de pantalla al llegar al marco |
width / height | Tamano en pixeles CSS de la caja |
name | Nombre del contexto, para poder apuntarle con target desde un enlace |
loading | eager (por defecto) o lazy, que retrasa la carga hasta que el marco se acerca a la pantalla |
allow | Politica de permisos: que capacidades del navegador puede usar el contenido |
allowfullscreen | Permite que el contenido pida pantalla completa |
referrerpolicy | Cuanta informacion sobre tu URL se envia al servidor del iframe |
sandbox | Restringe severamente lo que el contenido puede hacer |
Notaras que el ejemplo no lleva frameborder="0". Ese atributo esta obsoleto: el borde se quita con CSS, que es donde corresponde.
iframe {
border: 0;
}
El atributo title no es decorativo. Sin el, un usuario navegando con lector de pantalla escucha “marco” y nada mas. Con el, escucha de que se trata el marco antes de decidir si entra.
Iframes que mantienen su proporcion
Un iframe con width y height fijos se desborda en pantallas angostas. La solucion moderna es una linea de CSS:
.video {
width: 100%;
max-width: 720px;
margin: 0 auto;
}
.video iframe {
display: block;
width: 100%;
aspect-ratio: 16 / 9;
height: auto;
border: 0;
border-radius: 8px;
}
<div class="video">
<iframe
src="https://www.youtube.com/embed/LeYLP5uerGQ"
title="Video del capitulo"
loading="lazy"
allowfullscreen>
</iframe>
</div>
La propiedad aspect-ratio reserva el alto proporcional al ancho, asi que el marco mantiene su forma 16:9 en cualquier pantalla. Antes de que existiera habia que usar un truco con padding-bottom: 56.25%, que todavia veras en codigo antiguo.
srcdoc: contenido incrustado sin archivo externo
Si quieres una vista previa aislada sin cargar nada de la red, srcdoc recibe HTML directamente:
<iframe
title="Vista previa del componente"
width="400"
height="200"
srcdoc="
<!DOCTYPE html>
<html lang='es'>
<head><meta charset='utf-8'></head>
<body style='font-family: sans-serif; background: #111; color: #eee;'>
<p>Este documento vive dentro del atributo srcdoc.</p>
</body>
</html>
">
</iframe>
Fijate en un detalle mecanico: el valor de srcdoc va entre comillas dobles, asi que dentro del HTML incrustado usamos comillas simples. Si necesitas comillas dobles dentro, hay que escaparlas como ".
srcdoc es util para documentacion de componentes, para sandboxes de ejemplos de codigo, y para aislar contenido que no controlas.
Iframes y target: la conexion con el capitulo anterior
En el capitulo 6 vimos los valores de target y mencionamos _parent y _top sin poder explicarlos del todo, porque no habiamos visto los iframes. Ahora si.
stateDiagram-v2
[*] --> Top: Documento raiz (la pestana)
Top --> Padre: contiene
Padre --> Hijo: contiene un iframe
note right of Top
target="_top"
Reemplaza toda la pestana.
Rompe cualquier anidamiento.
end note
note right of Padre
target="_parent"
Reemplaza el documento que
contiene a este iframe.
end note
note right of Hijo
target="_self" (por defecto)
Navega dentro del mismo iframe.
target="_blank"
Abre una pestana nueva.
target="nombreDelIframe"
Navega dentro de ESE iframe.
end note
El ultimo caso es el mas util y el menos conocido: un enlace puede cargar su destino dentro de un iframe concreto, si el iframe tiene name y el enlace apunta a ese nombre. Es, en esencia, el viejo patron de menu con frames, pero hecho correctamente:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Visor con menu</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; }
.disposicion { display: flex; min-height: 100vh; }
nav { width: 200px; padding: 1rem; background: #1b1b1f; }
nav a { display: block; color: #9ecbff; padding: 0.5rem 0; }
iframe { flex: 1; border: 0; }
</style>
</head>
<body>
<div class="disposicion">
<nav aria-label="Secciones de la documentacion">
<a href="/tecnologias/superclubnet/00-indice/" target="visor">Indice</a>
<a href="/tecnologias/superclubnet/06-enlaces-uri-url-y-rutas/" target="visor">Capitulo 6</a>
<a href="/tecnologias/superclubnet/08-javascript-tipos-variables-y-strings/" target="visor">Capitulo 8</a>
</nav>
<iframe
name="visor"
title="Visor de documentacion"
src="/tecnologias/superclubnet/00-indice/">
</iframe>
</div>
</body>
</html>
Este patron tiene el mismo defecto que los frames antiguos: la URL de la barra de direcciones no cambia, asi que el usuario no puede compartir ni marcar lo que esta viendo. Sirve para herramientas internas o visores de previsualizacion, no para la estructura de un sitio publico.
Seguridad: sandbox
Un iframe carga codigo que tu no escribiste, ejecutandose en el navegador de tu usuario. El atributo sandbox te deja apretar las tuercas.
La regla es contraintuitiva: poner sandbox sin valor aplica todas las restricciones a la vez. Luego vas devolviendo permisos uno por uno con los tokens que incluyas.
<!-- Maxima restriccion: sin scripts, sin formularios,
sin popups, tratado como origen unico. -->
<iframe src="contenido-externo.html" title="Contenido no confiable" sandbox></iframe>
<!-- Restringido, pero se le devuelve la capacidad de ejecutar scripts
y de enviar formularios. -->
<iframe
src="widget.html"
title="Widget de terceros"
sandbox="allow-scripts allow-forms">
</iframe>
Token de sandbox | Permiso que devuelve |
|---|---|
allow-scripts | Ejecutar JavaScript |
allow-forms | Enviar formularios |
allow-same-origin | Tratar el contenido con su origen real en vez de un origen unico y opaco |
allow-popups | Abrir ventanas o pestanas nuevas |
allow-popups-to-escape-sandbox | Que las ventanas abiertas no hereden las restricciones |
allow-modals | Usar dialogos como alert, confirm y prompt |
allow-downloads | Iniciar descargas de archivos |
allow-top-navigation | Navegar la pestana completa |
allow-top-navigation-by-user-activation | Navegar la pestana solo tras un gesto del usuario |
allow-presentation | Iniciar sesiones de presentacion |
allow-pointer-lock | Capturar el puntero del raton |
allow-orientation-lock | Bloquear la orientacion de pantalla |
Hay una combinacion que anula el proposito del sandbox: allow-scripts junto con allow-same-origin en contenido del mismo origen que tu sitio. Con ambos permisos, el codigo del iframe puede alcanzar el documento padre y, entre otras cosas, quitarse el propio atributo sandbox. Si el contenido no es de confianza, no combines esos dos tokens.
Seguridad: allow y la politica de permisos
Mientras sandbox controla capacidades del documento (scripts, formularios, navegacion), allow controla el acceso a capacidades del dispositivo y del navegador: camara, microfono, ubicacion, reproduccion automatica y demas.
<!-- Un mapa que puede pedir la ubicacion del usuario,
pero no la camara ni el microfono. -->
<iframe
src="https://ejemplo.cl/mapa"
title="Mapa interactivo"
allow="geolocation">
</iframe>
<!-- Una videollamada incrustada que necesita camara y microfono,
pero solo si el origen es exactamente ese. -->
<iframe
src="https://reunion.ejemplo.cl/sala/42"
title="Sala de reunion"
allow="camera 'src'; microphone 'src'; display-capture 'src'">
</iframe>
Nombres de capacidades habituales: accelerometer, autoplay, camera, clipboard-write, display-capture, encrypted-media, fullscreen, geolocation, gyroscope, microphone, midi, payment, picture-in-picture, web-share.
La regla practica es la de siempre en seguridad: concede lo minimo. Si el contenido incrustado no necesita la camara, no la pongas en allow “por si acaso”.
Seguridad: que tu sitio no sea incrustado
Todo lo anterior te protege de lo que incrustas. Falta el reverso: impedir que otros incrusten tu sitio dentro del suyo. Ese ataque se llama clickjacking: alguien carga tu pagina en un iframe transparente sobre su propia interfaz, y el usuario cree que pulsa un boton inocente cuando en realidad esta pulsando un boton de tu aplicacion.
La defensa no es HTML, son cabeceras HTTP que envia tu servidor:
Content-Security-Policy: frame-ancestors 'self' https://socio-confiable.cl
frame-ancestors declara quien puede incrustarte. Con 'none' nadie puede; con 'self' solo tu propio sitio. La cabecera antigua equivalente es:
X-Frame-Options: SAMEORIGIN
X-Frame-Options acepta DENY y SAMEORIGIN. Se mantiene por compatibilidad, pero frame-ancestors es la forma actual y la que gana cuando ambas estan presentes.
Rendimiento: loading="lazy"
Un iframe cuesta caro. Si tienes tres videos en un articulo largo y el usuario nunca baja hasta el tercero, no tiene sentido descargarlo.
<iframe
src="https://www.youtube.com/embed/LeYLP5uerGQ"
title="Video del capitulo"
loading="lazy"
width="560"
height="315">
</iframe>
Con loading="lazy" el navegador retrasa la carga hasta que el marco se aproxima al area visible. Es una linea que puede recortar mucho la carga inicial de una pagina.
Para casos extremos existe el patron de “fachada”: muestras una imagen estatica con un boton de reproduccion y solo insertas el iframe real cuando el usuario pulsa. Con lo que sabes hasta ahora ya puedes escribirlo:
.fachada {
position: relative;
width: 100%;
max-width: 720px;
aspect-ratio: 16 / 9;
border: 0;
padding: 0;
cursor: pointer;
background-size: cover;
background-position: center;
}
.fachada::after {
content: "\25B6";
position: absolute;
inset: 0;
display: grid;
place-items: center;
font-size: 3rem;
color: #fff;
background: rgba(0, 0, 0, 0.35);
}
<button
class="fachada"
id="reproductor"
style="background-image: url('miniatura.jpg')"
aria-label="Reproducir el video del capitulo 7">
</button>
<script>
const boton = document.getElementById('reproductor');
boton.addEventListener('click', function () {
const marco = document.createElement('iframe');
marco.src = 'https://www.youtube.com/embed/LeYLP5uerGQ?autoplay=1';
marco.title = 'Video del capitulo 7';
marco.allow = 'autoplay; encrypted-media; picture-in-picture';
marco.allowFullscreen = true;
marco.style.cssText = 'width:100%;aspect-ratio:16/9;border:0';
// Reemplazamos el boton por el iframe real.
boton.replaceWith(marco);
});
</script>
Hasta que el usuario pulsa, la pagina no descarga ni un byte del servicio externo.
Comunicacion entre padre e iframe
Cuando el iframe carga contenido de otro origen, tu JavaScript no puede leer su DOM ni el suyo el tuyo. El unico puente autorizado es postMessage.
En el documento padre:
<script>
const marco = document.getElementById('miMarco');
// Enviar un mensaje al iframe. El segundo argumento es el
// origen esperado: nunca uses '*' con datos sensibles.
marco.addEventListener('load', function () {
marco.contentWindow.postMessage(
{ tipo: 'tema', valor: 'oscuro' },
'https://ejemplo.cl'
);
});
// Recibir mensajes del iframe.
window.addEventListener('message', function (evento) {
// Verificar SIEMPRE el origen antes de confiar en los datos.
if (evento.origin !== 'https://ejemplo.cl') return;
console.log('El iframe dice:', evento.data);
});
</script>
Y dentro del iframe:
<script>
window.addEventListener('message', function (evento) {
if (evento.origin !== 'https://tusitio.cl') return;
if (evento.data.tipo === 'tema') {
document.body.dataset.tema = evento.data.valor;
}
});
// Avisar al padre de la altura real del contenido.
window.parent.postMessage(
{ tipo: 'altura', valor: document.body.scrollHeight },
'https://tusitio.cl'
);
</script>
La verificacion de evento.origin no es opcional. Sin ella, cualquier pagina podria enviarte mensajes falsos y tu codigo los trataria como legitimos. Es el error de seguridad mas frecuente al usar postMessage.
Si el iframe carga contenido del mismo origen, el aislamiento no aplica y puedes acceder directamente a marco.contentDocument para leer y modificar su DOM. Pero incluso en ese caso, postMessage suele ser mejor diseno porque no acopla los dos documentos.
Parte 3: divisores de contenido
Pasamos del contenido incrustado a la estructura del propio documento. Un divisor es una etiqueta cuyo trabajo no es mostrar algo concreto, sino agrupar y separar. Sirven para dos audiencias distintas: el CSS, que necesita algo a lo que aplicar estilos, y las herramientas que interpretan la pagina (lectores de pantalla, buscadores, modos de lectura), que necesitan saber que parte es que.
<hr>: una separacion tematica
<hr> se traduce a menudo como “linea horizontal”, y ese nombre confunde. En HTML moderno significa ruptura tematica: el tema cambia. Que el navegador lo dibuje como una linea es solo el estilo por defecto.
<article>
<h2>Primera parte: los mapas de imagen</h2>
<p>Contenido sobre map y area.</p>
<hr>
<h2>Segunda parte: los iframes</h2>
<p>Contenido sobre contextos anidados.</p>
</article>
Es un elemento vacio: no tiene etiqueta de cierre y no contiene nada.
Si lo que quieres es una linea decorativa que separe visualmente dos bloques sin implicar cambio de tema, la herramienta correcta es CSS:
.tarjeta + .tarjeta {
border-top: 1px solid #333;
}
Y si de todas formas usas <hr>, puedes estilizarlo:
hr {
border: 0;
height: 2px;
background: linear-gradient(to right, transparent, #6f6, transparent);
margin: 3rem 0;
}
<div> y <span>: los contenedores sin significado
Estos dos son deliberadamente neutros: no dicen nada sobre el contenido, solo lo agrupan.
<div>es de bloque: ocupa todo el ancho disponible y empieza en una linea nueva.<span>es en linea: ocupa solo lo que necesita y convive con el texto a su alrededor.
<div class="alerta">
<p>Cuidado, piso resbaloso.</p>
<p>Se recomienda usar calzado antideslizante.</p>
</div>
<p>
El precio final es de
<span class="destacado">$19.990</span>
con impuestos incluidos.
</p>
La regla de uso es simple: usa <div> o <span> cuando necesitas un gancho para CSS o JavaScript y ninguna otra etiqueta describe mejor lo que hay dentro. Si el contenido es una lista, usa <ul>. Si es un parrafo, usa <p>. Si es la cabecera de la pagina, usa <header>. El <div> es el ultimo recurso, no el primero.
Los contenedores semanticos
Estos si dicen algo sobre el contenido que envuelven.
| Etiqueta | Que significa | Cuantos por documento |
|---|---|---|
<header> | Contenido introductorio de la pagina o de una seccion: titulo, logo, buscador | Varios, uno por seccion |
<nav> | Un bloque de enlaces de navegacion importante | Varios, cada uno etiquetado |
<main> | El contenido principal y unico del documento | Solo uno visible |
<section> | Un agrupamiento tematico, normalmente con su propio encabezado | Varios |
<article> | Una pieza de contenido con sentido completo por si sola | Varios |
<aside> | Contenido relacionado pero tangencial: barra lateral, notas, publicidad | Varios |
<footer> | Cierre de la pagina o de una seccion: creditos, contacto, licencia | Varios, uno por seccion |
Hay reglas concretas que conviene memorizar:
<main>es unico y no debe estar dentro de<article>,<aside>,<footer>,<header>ni<nav>. Marca el contenido central, lo que quedaria si quitaras cabecera, menu y pie.<header>no es<head>.<head>va antes que<body>y contiene metadatos invisibles;<header>es contenido visible.<section>casi siempre lleva un encabezado. Una<section>sinh1ah6es senal de que en realidad querias un<div>.<nav>se reserva para navegacion principal. No hace falta envolver cada grupo de dos enlaces. Si hay varios<nav>, distinguelos conaria-label.
Como decidir entre section, article y div
flowchart TD
A["Necesito agrupar<br/>este contenido"] --> B{"Hay una etiqueta<br/>especifica que ya lo<br/>describa? (ul, table,<br/>figure, form...)"}
B -- Si --> C["Usala.<br/>No envuelvas por envolver"]
B -- No --> D{"El contenido tiene<br/>sentido completo si lo<br/>saco de la pagina y lo<br/>publico solo?"}
D -- Si --> E["article<br/>(post, comentario,<br/>ficha de producto,<br/>tarjeta de noticia)"]
D -- No --> F{"Es un bloque tematico<br/>con su propio<br/>encabezado?"}
F -- Si --> G["section<br/>(capitulo, apartado,<br/>bloque de la landing)"]
F -- No --> H{"Es solo un gancho<br/>para CSS o JavaScript?"}
H -- Si --> I["div si es de bloque<br/>span si es en linea"]
H -- No --> J["Probablemente no<br/>necesitas envolver nada"]
El caso que mas se discute es article dentro de section y viceversa. Ambos son validos y ambos ocurren:
- Una
<section>llamada “Ultimas noticias” que contiene varios<article>: correcto. - Un
<article>de blog que internamente tiene<section>para “Introduccion”, “Desarrollo” y “Conclusion”: tambien correcto.
Esqueleto completo de una pagina
graph TD
BODY["body"]
BODY --> HEADER["header<br/>logo + titulo del sitio"]
HEADER --> NAV["nav aria-label='Principal'<br/>menu de secciones"]
BODY --> MAIN["main<br/>contenido unico de esta pagina"]
MAIN --> ART["article<br/>el post completo"]
ART --> AHEAD["header<br/>titulo del post + fecha"]
ART --> S1["section<br/>Introduccion"]
ART --> HR["hr<br/>ruptura tematica"]
ART --> S2["section<br/>Desarrollo"]
ART --> AFOOT["footer<br/>autor + etiquetas"]
MAIN --> ASIDE["aside<br/>articulos relacionados"]
BODY --> FOOTER["footer<br/>copyright + contacto"]
Y su traduccion a codigo, completo y funcional:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Estructura semantica completa</title>
<style>
:root {
--fondo: #12121a;
--texto: #e6e6ef;
--acento: #7dd3fc;
--borde: #2a2a3a;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.6;
background: var(--fondo);
color: var(--texto);
}
header, main, footer { padding: 1.5rem; }
body > header { border-bottom: 1px solid var(--borde); }
nav ul {
list-style: none;
display: flex;
gap: 1.25rem;
padding: 0;
margin: 0.75rem 0 0;
}
nav a { color: var(--acento); text-decoration: none; }
nav a:hover, nav a:focus-visible { text-decoration: underline; }
main {
display: grid;
grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
gap: 2rem;
max-width: 1100px;
margin: 0 auto;
}
@media (max-width: 720px) {
main { grid-template-columns: 1fr; }
}
article {
border: 1px solid var(--borde);
border-radius: 10px;
padding: 1.5rem;
}
article > header { padding: 0 0 1rem; }
article > footer {
padding: 1rem 0 0;
border-top: 1px solid var(--borde);
font-size: 0.9rem;
}
aside {
border-left: 3px solid var(--acento);
padding-left: 1rem;
font-size: 0.95rem;
}
hr { border: 0; height: 1px; background: var(--borde); margin: 2rem 0; }
body > footer {
border-top: 1px solid var(--borde);
text-align: center;
font-size: 0.9rem;
}
</style>
</head>
<body>
<header>
<h1>Super Club Net</h1>
<p>Notas de un curso de desarrollo web</p>
<nav aria-label="Navegacion principal">
<ul>
<li><a href="/tecnologias/superclubnet/00-indice/">Indice</a></li>
<li><a href="/tecnologias/superclubnet/06-enlaces-uri-url-y-rutas/">Capitulo 6</a></li>
<li><a href="/tecnologias/superclubnet/08-javascript-tipos-variables-y-strings/">Capitulo 8</a></li>
</ul>
</nav>
</header>
<main>
<article>
<header>
<h2>Mapas de imagen, iframes y divisores</h2>
<p><time datetime="2026-08-17">17 de agosto de 2026</time></p>
</header>
<section>
<h3>Mapas de imagen</h3>
<p>
Un mapa convierte zonas de una imagen en enlaces
independientes mediante map y area.
</p>
</section>
<hr>
<section>
<h3>Iframes</h3>
<p>
Un iframe crea un contexto de navegacion anidado con su
propio documento, aislado del documento padre.
</p>
</section>
<hr>
<section>
<h3>Divisores</h3>
<p>
Los contenedores semanticos describen el rol de cada bloque
en la pagina, no solo su apariencia.
</p>
</section>
<footer>
<p>Escrito por Artiko. Etiquetas: html, semantica, accesibilidad.</p>
</footer>
</article>
<aside aria-label="Contenido relacionado">
<h2>Tambien te puede servir</h2>
<ul>
<li><a href="/tecnologias/superclubnet/06-enlaces-uri-url-y-rutas/">URL y rutas</a></li>
<li><a href="/tecnologias/superclubnet/00-indice/">Todos los capitulos</a></li>
</ul>
</aside>
</main>
<footer>
<p>Contenido del curso Super Club Net. Todos los capitulos en el indice.</p>
</footer>
</body>
</html>
Ese documento no usa ni un solo <div>, y aun asi tiene una estructura completa con cabecera, menu, contenido principal, barra lateral y pie. No es una regla que haya que llevar al extremo, pero muestra hasta donde llega el vocabulario semantico antes de tener que recurrir a contenedores neutros.
Que ganas con la semantica
No es una cuestion estetica. Tiene efectos medibles:
| Beneficio | Como se manifiesta |
|---|---|
| Navegacion por regiones | Un lector de pantalla permite saltar directo a “principal”, “navegacion” o “pie” |
| Modo lectura | Firefox, Safari y otros detectan <article> y <main> para extraer el contenido limpio |
| Buscadores | Distinguen el contenido central del menu repetido en cada pagina |
| Mantenimiento | Leer el HTML de otra persona es mucho mas rapido cuando las etiquetas dicen que hacen |
| CSS mas limpio | Puedes escribir main article > header sin inventar tres clases |
Contenedores especializados que conviene conocer
Ademas de los generales, hay contenedores para casos especificos que resuelven problemas concretos:
<!-- Imagen o diagrama con su leyenda asociada -->
<figure>
<img src="diagrama-iframe.png" alt="Esquema de un contexto de navegacion anidado">
<figcaption>El iframe crea un documento independiente dentro del padre.</figcaption>
</figure>
<!-- Bloque plegable, sin JavaScript -->
<details>
<summary>Ver los tokens de sandbox menos usados</summary>
<p>allow-orientation-lock, allow-pointer-lock y allow-presentation.</p>
</details>
<!-- Cita de otra fuente, con su origen -->
<blockquote cite="https://developer.mozilla.org/es/docs/Web/HTML/Element/iframe">
<p>El elemento iframe representa un contexto de navegacion anidado.</p>
</blockquote>
<details> y <summary> merecen atencion: dan un comportamiento de acordeon completamente funcional, con soporte de teclado y accesibilidad incluidos, sin escribir una linea de JavaScript.
Tabla comparativa general del capitulo
| Elemento | Categoria | Es vacio | Rol principal | Cuidado principal |
|---|---|---|---|---|
<map> | Metadatos de imagen | No | Agrupar zonas con un nombre | El name debe coincidir exacto con el usemap |
<area> | Enlace geometrico | Si | Definir una zona pulsable | El alt es obligatorio si hay href |
<iframe> | Contenido incrustado | No | Cargar un documento anidado | Coste de rendimiento y superficie de seguridad |
<hr> | Flujo | Si | Marcar un cambio de tema | No usarlo como decoracion |
<div> | Flujo generico | No | Agrupar en bloque | Sin significado: ultimo recurso |
<span> | Flujo en linea | No | Agrupar dentro del texto | Sin significado: ultimo recurso |
<header> | Seccionado | No | Introduccion de una seccion | No confundir con <head> |
<nav> | Seccionado | No | Navegacion importante | Etiquetar si hay varios |
<main> | Seccionado | No | Contenido central unico | Solo uno, y no anidado en otros |
<section> | Seccionado | No | Bloque tematico | Debe llevar encabezado |
<article> | Seccionado | No | Pieza autonoma | Solo si tiene sentido fuera de contexto |
<aside> | Seccionado | No | Contenido tangencial | No para cualquier columna lateral decorativa |
<footer> | Seccionado | No | Cierre de una seccion | Puede haber varios |
Errores comunes
| Error | Causa | Solucion |
|---|---|---|
| El mapa de imagen no responde a ningun clic | El usemap no coincide con el name del <map>, o falta la almohadilla | Verifica que sea usemap="#nombre" y <map name="nombre">, con el mismo texto exacto y respetando mayusculas |
| Toda la imagen lleva al mismo destino | El <area shape="default"> esta escrito antes que los demas | Mueve la zona default al final del <map>: gana la primera coincidencia |
| Las zonas estan desplazadas en el movil | El navegador no reescala las coordenadas cuando la imagen se muestra a otro tamano | Recalcula coords con JavaScript o cambia a enlaces posicionados en porcentajes |
El validador marca el HTML como invalido en <area> | Hay un <area> con href pero sin alt | Anade un alt descriptivo, o quita el href si la zona no debe ser un enlace |
poly no dibuja la zona esperada | Se repitio el primer vertice al final o hay un numero impar de valores | Elimina el punto repetido y comprueba que coords tenga pares completos |
El iframe se ve en blanco y la consola muestra un error de frame-ancestors | El sitio de destino prohibe ser incrustado mediante CSP o X-Frame-Options | No hay solucion desde tu lado: usa la URL de incrustacion oficial del servicio o enlaza en vez de incrustar |
| El video de YouTube muestra un error dentro del iframe | Se uso la URL de la pagina (watch?v=) en vez de la de incrustacion | Usa https://www.youtube.com/embed/ID |
| Mi CSS no afecta al contenido del iframe | El iframe es un documento independiente con su propio arbol de estilos | Solo puedes estilizar la caja exterior; el interior lo controla el documento incrustado |
contentDocument devuelve null | El iframe carga otro origen y la politica de mismo origen lo bloquea | Usa postMessage para comunicarte, verificando siempre evento.origin |
| El sandbox no restringe nada | Se combino allow-scripts con allow-same-origin en contenido del mismo origen | Quita uno de los dos tokens si el contenido no es de confianza |
| La pagina tarda mucho en cargar con varios videos | Cada iframe descarga un documento completo desde el inicio | Anade loading="lazy" o usa el patron de fachada con miniatura |
| El iframe se desborda en pantallas pequenas | width y height fijos en pixeles | Usa width: 100% con aspect-ratio en CSS |
| Un lector de pantalla anuncia solo “marco” | Falta el atributo title en el <iframe> | Anade un title que describa el contenido incrustado |
El validador se queja de dos <main> | Hay mas de un <main> visible en el documento | Deja uno solo, o marca los demas con el atributo hidden |
<section> sin encabezado genera advertencias de accesibilidad | Se uso <section> como si fuera <div> | Anade un encabezado, o cambia a <div> si el bloque no es tematico |
El <hr> aparece donde no se queria una linea | Se uso <hr> como separador decorativo | Usa border-top en CSS y reserva <hr> para cambios de tema |
<header> no muestra nada en la parte superior | Se confundio con <head> y se puso fuera del <body> | <header> va dentro de <body>; <head> va antes y contiene metadatos |
Ejercicios propuestos
1. Panel de coordenadas. Toma una foto tuya o cualquier imagen con al menos tres elementos distinguibles. Usando el medidor de coordenadas de este capitulo, anota las coordenadas de cada elemento y construye un <map> con un rect, un circle y un poly. Anade un default que lleve a una pagina de ayuda.
2. Mapa que sobrevive al movil. Toma el mapa del ejercicio 1 y hazlo adaptable de dos maneras distintas: primero con el script de reescalado y ResizeObserver, y despues reescribiendolo con enlaces posicionados en porcentajes. Compara ambos resultados reduciendo la ventana del navegador.
3. Zona muerta. Anade al mapa del ejercicio 1 una region central sin href que impida que el clic caiga en la zona default. Verifica que efectivamente no pasa nada al pulsar ahi.
4. Visor con menu lateral. Construye la pagina con <nav> y un <iframe name="visor">. Anade cuatro enlaces con target="visor". Despues anade un quinto enlace con target="_top" y observa la diferencia de comportamiento.
5. Escalera de sandbox. Crea un archivo travieso.html que intente tres cosas: ejecutar un alert, enviar un formulario y abrir una ventana nueva. Incrustalo cuatro veces en una pagina: sin sandbox, con sandbox vacio, con sandbox="allow-scripts" y con sandbox="allow-scripts allow-modals allow-popups". Documenta en una tabla que funciona en cada caso.
6. Fachada de video. Implementa el patron de fachada. Abre la pestana de red de las herramientas de desarrollo y compara el numero de peticiones antes y despues de pulsar el boton.
7. Puente postMessage. Crea dos archivos en el mismo directorio: padre.html con un iframe y un campo de texto, e hijo.html que reciba lo escrito y lo muestre en su cuerpo. Anade la verificacion de evento.origin en ambos lados y comprueba que el mensaje se descarta si cambias el origen esperado.
8. Deconstruccion semantica. Elige una pagina de un sitio real que uses a diario. Dibuja en papel su estructura y despues escribe el esqueleto HTML equivalente usando solo header, nav, main, section, article, aside y footer. Anota cuantas veces necesitaste recurrir a un <div> y por que.
9. Auditoria de div. Toma cualquier pagina que hayas escrito en capitulos anteriores y cuenta sus <div>. Reemplaza cada uno por la etiqueta semantica correcta cuando exista. Justifica cada superviviente.
10. Todo junto. Construye una pagina de una sola vista que combine los tres temas: un mapa de imagen adaptable como cabecera visual, un <iframe> con loading="lazy" en el contenido principal, y una estructura semantica completa con header, nav, main, article, aside y footer. Pasala por el validador de HTML del W3C hasta que no reporte errores.
Lo que viene
Con este capitulo cerramos el recorrido por HTML. Ya sabes describir texto, estructurar tablas, insertar imagenes y multimedia, construir enlaces con rutas correctas, convertir imagenes en superficies interactivas, incrustar documentos ajenos controlando lo que pueden hacer, y organizar todo eso en una estructura que tanto una persona como una maquina pueden entender.
Hasta aqui, sin embargo, todo ha sido estatico: el documento se describe una vez y el navegador lo pinta. Las unicas piezas moviles que aparecieron fueron pequenos scripts que usamos sin explicar de verdad: el medidor de coordenadas, el reescalado del mapa, la fachada del video, el puente de postMessage.
Es hora de entender ese lenguaje desde la base. En el capitulo 8 empezamos con JavaScript: como se incluye en la pagina, que tipos de datos existen, como se declaran variables con let y const, que diferencia hay entre valores primitivos y objetos, y como trabajar con cadenas de texto en profundidad. Ese es el punto en el que la pagina deja de ser un documento y empieza a ser un programa.
El recorrido completo del curso esta en el indice.