Imágenes en la web: la etiqueta img, formatos, posicionamiento y fondos con CSS
Imágenes en la web: la etiqueta img, formatos, posicionamiento y fondos con CSS
En el capítulo 4 armamos tablas y aprendimos a darles estilo: bordes colapsados, encabezados fijos, filas alternadas y celdas alineadas. Todo ese contenido era texto. Ahora vamos a incorporar el otro gran tipo de contenido de la web: las imágenes.
Una imagen no es texto que el navegador dibuja con una fuente, sino un archivo externo que hay que descargar por separado. Eso cambia varias cosas a la vez: aparece el concepto de ruta, aparece el concepto de formato, aparece el peso en kilobytes, aparece el problema de qué mostrar mientras la imagen todavía no llega y aparece el problema de qué mostrar si nunca llega. Este capítulo cubre todo ese recorrido, desde la etiqueta <img> más simple hasta fondos compuestos con CSS y resolución de rutas relativas.
El índice completo del curso está en /tecnologias/superclubnet/00-indice/.
La etiqueta img: lo mínimo que funciona
El elemento <img> incrusta una imagen en el documento. Es un elemento vacío: no tiene contenido entre etiquetas y por lo tanto no lleva etiqueta de cierre. Toda su información viaja en atributos.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Primera imagen</title>
</head>
<body>
<h1>Consola en exhibición</h1>
<img src="consola.png" alt="Consola de videojuegos de 64 bits con dos controles conectados">
</body>
</html>
Con dos atributos ya tenemos una imagen funcional:
src(source, origen): la ruta del archivo de imagen. Es obligatorio; sin él no hay nada que mostrar.alt(alternative text, texto alternativo): la descripción textual de la imagen. Es obligatorio en la práctica, aunque el navegador no se queje si falta.
Todo lo demás —tamaño, borde, posición, comportamiento de carga— es opcional y se controla con más atributos o con CSS.
Cómo llega realmente la imagen a la pantalla
Vale la pena entender la secuencia completa, porque explica muchos comportamientos que después parecen misteriosos: por qué la página “salta” mientras carga, por qué a veces aparece un ícono roto, por qué el texto alternativo se ve un instante.
sequenceDiagram
participant U as Usuario
participant N as Navegador
participant S as Servidor
U->>N: Escribe la URL de la pagina
N->>S: GET /pagina.html
S-->>N: 200 OK, cuerpo HTML
N->>N: Parsea el HTML y construye el DOM
N->>N: Encuentra un elemento img con src
N->>N: Resuelve la ruta relativa contra la URL base
N->>S: GET /consola.png
N->>N: Sigue parseando el resto del documento
S-->>N: 200 OK, bytes de la imagen
N->>N: Decodifica el archivo a mapa de pixeles
N->>N: Calcula el tamaño final y repinta
N-->>U: Muestra la imagen en su lugar
Tres consecuencias prácticas de este diagrama:
- La petición de la imagen es independiente de la del HTML. Una página con veinte imágenes hace veintiún viajes al servidor como mínimo. Cada archivo cuesta latencia además de bytes.
- El navegador no espera la imagen para seguir leyendo el HTML. Por eso el texto aparece antes que las fotos.
- Hasta que la imagen no llega, el navegador no sabe cuánto mide salvo que se lo digamos nosotros. Ese es el origen del “salto” de contenido que veremos más adelante.
Los atributos del elemento img
| Atributo | Para qué sirve | Ejemplo |
|---|---|---|
src | Ruta del archivo de imagen | src="fotos/mapa.jpg" |
alt | Texto alternativo, leído por lectores de pantalla y mostrado si la imagen falla | alt="Mapa del circuito" |
title | Texto emergente al dejar el cursor encima | title="Circuito Arcoíris" |
width | Ancho intrínseco declarado, en píxeles, sin unidad | width="640" |
height | Alto intrínseco declarado, en píxeles, sin unidad | height="360" |
loading | eager carga de inmediato, lazy difiere hasta que se acerque al viewport | loading="lazy" |
decoding | Sugerencia de decodificación: sync, async o auto | decoding="async" |
srcset | Lista de archivos alternativos con su tamaño, para pantallas distintas | srcset="foto-640.jpg 640w, foto-1280.jpg 1280w" |
sizes | Ancho que ocupará la imagen en el diseño, para elegir de srcset | sizes="(max-width: 600px) 100vw, 50vw" |
referrerpolicy | Qué información de origen se envía al pedir la imagen | referrerpolicy="no-referrer" |
crossorigin | Control de peticiones entre orígenes distintos | crossorigin="anonymous" |
Los atributos width y height en HTML se escriben sin unidad: width="640", nunca width="640px". En CSS es al revés: ahí sí se escribe la unidad.
alt y title no son lo mismo
Es el punto donde más gente se confunde, y la diferencia es concreta.
altreemplaza a la imagen. Es lo que un lector de pantalla lee en voz alta y lo que el navegador muestra si el archivo no carga. Debe describir el contenido y la función de la imagen para alguien que no puede verla.titleacompaña a la imagen. Aparece como globo de ayuda al pasar el mouse. No lo ven quienes navegan con teclado ni quienes usan pantallas táctiles, así que nunca debe contener información imprescindible.
<!-- Imagen informativa: alt describe lo que aporta -->
<img
src="podio.jpg"
alt="Tres corredores en el podio; el primer lugar levanta un trofeo dorado"
title="Final del campeonato 1997"
>
<!-- Imagen decorativa: alt vacío para que el lector de pantalla la ignore -->
<img src="separador-ondas.svg" alt="">
<!-- Imagen que funciona como boton: alt describe la accion, no el dibujo -->
<a href="/carrito/">
<img src="icono-carrito.svg" alt="Ver el carrito de compras">
</a>
Reglas que conviene memorizar:
- Si la imagen aporta información,
altla describe. - Si la imagen es puramente decorativa,
alt=""(vacío, pero presente). Omitir el atributo no es lo mismo: sinalt, muchos lectores de pantalla leen el nombre del archivo, que suele ser ruido. - Si la imagen es el contenido de un enlace o botón,
altdescribe el destino o la acción. - No empieces con “imagen de” ni “foto de”: el lector de pantalla ya anuncia que es una imagen.
width, height y el salto de contenido
Cuando el navegador dibuja el texto de la página todavía no descargó las imágenes, así que reserva cero espacio para ellas. Cuando la imagen finalmente llega, el navegador la inserta y todo lo que estaba debajo se desplaza hacia abajo. Ese salto es incómodo: el usuario estaba a punto de tocar un enlace y el enlace se movió.
La solución es declarar las dimensiones en el HTML:
<img
src="portada.jpg"
alt="Portada del juego con el logotipo sobre un fondo azul"
width="1200"
height="675"
>
Con esos números el navegador calcula la relación de aspecto (1200 ÷ 675 = 16/9) y reserva la caja del tamaño correcto antes de que llegue un solo byte de la imagen. Cuando además usamos CSS fluido, la combinación correcta es esta:
img {
max-width: 100%; /* nunca se desborda de su contenedor */
height: auto; /* mantiene la proporcion original */
}
height: auto junto con los atributos width y height del HTML es la fórmula que evita el salto y a la vez permite que la imagen se encoja en pantallas chicas. Los navegadores modernos derivan un aspect-ratio implícito de esos dos atributos justamente para esto.
flowchart TD
A["El navegador encuentra un img"] --> B{"Tiene width y height<br/>en el HTML?"}
B -->|Si| C["Reserva la caja con<br/>la proporcion correcta"]
B -->|No| D["Reserva altura cero"]
C --> E["Llega la imagen"]
D --> F["Llega la imagen"]
E --> G["Se pinta dentro de la caja<br/>ya reservada, sin salto"]
F --> H["La caja crece de golpe y<br/>empuja el contenido de abajo"]
Formatos de imagen: cuál usar y por qué
Elegir mal el formato es la causa número uno de páginas pesadas. Cada formato está optimizado para un tipo de contenido distinto.
| Formato | Extensión | Compresión | Transparencia | Animación | Fuerte en | Débil en |
|---|---|---|---|---|---|---|
| JPEG | .jpg, .jpeg | Con pérdida | No | No | Fotografías, degradados suaves | Texto, líneas nítidas, transparencia |
| PNG | .png | Sin pérdida | Sí, con canal alfa | No (salvo APNG) | Ilustraciones, capturas de pantalla, logotipos | Fotografías: archivo muy pesado |
| GIF | .gif | Sin pérdida, 256 colores | Sí, pero binaria | Sí | Animaciones cortas antiguas | Calidad de color y peso |
| WebP | .webp | Con y sin pérdida | Sí, con canal alfa | Sí | Sustituto general de JPG y PNG, 25 a 35 % más liviano | Herramientas de edición muy antiguas |
| AVIF | .avif | Con y sin pérdida | Sí, con canal alfa | Sí | Máxima compresión, alto rango dinámico | Codificación lenta, soporte más reciente |
| SVG | .svg | Vectorial, texto XML | Sí | Sí, con SMIL o CSS | Logotipos, íconos, diagramas: escala sin perder nitidez | Fotografías |
Un par de precisiones que suelen faltar en las explicaciones rápidas:
- Con pérdida significa que el algoritmo descarta información que el ojo humano nota poco. Guardar y volver a guardar un JPEG degrada la imagen cada vez, porque la pérdida se acumula.
- Sin pérdida significa que al descomprimir se recupera exactamente el archivo original. PNG es sin pérdida, por eso una foto en PNG pesa varias veces lo que la misma foto en JPEG.
- SVG no tiene píxeles. Es un archivo de texto que describe formas: círculos, rutas, polígonos. El navegador lo dibuja a la resolución que haga falta, así que se ve nítido en cualquier pantalla y a cualquier zoom. Un ícono SVG puede pesar menos de 1 KB.
Árbol de decisión de formato
flowchart TD
A["Que tipo de imagen es?"] --> B{"Es un dibujo hecho<br/>de formas geometricas?"}
B -->|Si| C["SVG"]
B -->|No| D{"Necesita transparencia?"}
D -->|Si| E{"Es una fotografia?"}
D -->|No| F{"Es una fotografia?"}
E -->|Si| G["AVIF o WebP<br/>con canal alfa"]
E -->|No| H["PNG, o WebP<br/>sin perdida"]
F -->|Si| I["AVIF, con WebP<br/>o JPEG de respaldo"]
F -->|No| J["PNG o WebP<br/>sin perdida"]
C --> K["Verificar el peso final<br/>y las dimensiones reales"]
G --> K
H --> K
I --> K
J --> K
Un mismo archivo en varios formatos: el elemento picture
Si queremos servir AVIF a quien lo soporte, WebP a quien no, y JPEG como último recurso, existe el elemento <picture>. El navegador evalúa las fuentes en orden y usa la primera que entiende.
<picture>
<source srcset="paisaje.avif" type="image/avif">
<source srcset="paisaje.webp" type="image/webp">
<img
src="paisaje.jpg"
alt="Vista aerea de un circuito de carreras rodeado de arboles"
width="1600"
height="900"
>
</picture>
Detalles importantes de <picture>:
- El
<img>interior no es opcional: es el elemento que realmente se dibuja y el que llevaalt,widthyheight. Los<source>solo le indican qué archivo usar. - Si el navegador no entiende
<picture>, ignora las etiquetas desconocidas y muestra el<img>. Es degradación elegante gratuita. typeusa el tipo MIME del formato:image/avif,image/webp,image/jpeg,image/png,image/svg+xml.
Imágenes responsivas con srcset y sizes
Servir una imagen de 2400 píxeles de ancho a un teléfono que la muestra a 360 píxeles desperdicia datos. srcset ofrece varias versiones del mismo archivo y sizes le dice al navegador qué ancho ocupará la imagen en el diseño, para que elija la más adecuada antes de descargar nada.
<img
src="circuito-800.jpg"
srcset="
circuito-400.jpg 400w,
circuito-800.jpg 800w,
circuito-1600.jpg 1600w
"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 600px"
alt="Curva peraltada del circuito vista desde la tribuna"
width="800"
height="450"
>
Cómo se lee eso:
400wno significa “mostrar a 400 píxeles”: es el ancho real en píxeles del archivo. Se llama descriptor de ancho.sizesdescribe el ancho de presentación: hasta 600 píxeles de viewport la imagen ocupa el 100 % del ancho, hasta 1200 ocupa la mitad, y de ahí en adelante 600 píxeles fijos.- El navegador combina
sizescon la densidad de la pantalla y elige el archivo delsrcsetque mejor cubra esa medida. srcsigue presente como respaldo para navegadores que ignorensrcset.
Carga diferida
<img
src="galeria-07.jpg"
alt="Sala de maquinas arcade con gabinetes iluminados"
width="900"
height="600"
loading="lazy"
decoding="async"
>
loading="lazy"pospone la descarga hasta que la imagen esté por entrar en pantalla. Ideal para galerías largas y contenido debajo del pliegue.loading="eager"es el comportamiento por defecto y conviene dejarlo en las imágenes que se ven apenas carga la página, como una portada.decoding="async"permite que la decodificación no bloquee el pintado del resto.
Aplicar lazy a la imagen principal de la página es contraproducente: retrasa justo la imagen que el usuario ya está mirando.
Dimensionar y recortar con CSS
Los atributos HTML declaran el tamaño intrínseco. El tamaño final en pantalla lo decide CSS.
/* Base sana para cualquier proyecto */
img {
display: block; /* elimina el hueco de linea base bajo la imagen */
max-width: 100%; /* nunca se desborda del contenedor */
height: auto; /* conserva la proporcion */
}
Ese display: block merece explicación. Por defecto <img> es un elemento en línea reemplazado: se comporta como una palabra dentro de un renglón y por lo tanto se apoya sobre la línea base del texto, dejando unos pocos píxeles de espacio debajo. Ese hueco fantasma es la causa de la clásica franja inexplicable bajo una imagen dentro de un contenedor con fondo. Con display: block desaparece; con vertical-align: middle también.
object-fit y object-position
Cuando forzamos un ancho y un alto que no respetan la proporción original, la imagen se deforma. object-fit controla cómo se acomoda el contenido dentro de esa caja.
/* Todas las imagenes ocupan la misma caja de 100 % por 200 px */
.galeria img {
display: block;
width: 100%;
height: 200px;
}
.fill img { object-fit: fill; } /* estira y deforma para llenar */
.contain img { object-fit: contain; } /* entra completa, deja bandas vacias */
.cover img { object-fit: cover; } /* llena la caja recortando lo que sobra */
.none img { object-fit: none; } /* tamano original, se recorta si no cabe */
.down img { object-fit: scale-down; } /* el menor entre none y contain */
| Valor | Deforma | Recorta | Deja espacio vacío |
|---|---|---|---|
fill | Sí | No | No |
contain | No | No | Sí |
cover | No | Sí | No |
none | No | Sí | Sí |
scale-down | No | No | Sí |
object-position elige qué parte se conserva cuando cover recorta:
.retrato img {
width: 100%;
height: 320px;
object-fit: cover;
object-position: 50% 20%; /* centrado horizontal, mas arriba en vertical */
}
Es el ajuste que salva los retratos: con cover y posición centrada, el recorte suele cortar las cabezas; subir el punto de anclaje al 20 % conserva los rostros.
Posicionar imágenes en la página
El flujo natural
Una imagen sin CSS se comporta como una palabra: queda en el renglón, junto al texto que la rodea. Ese es el punto de partida sobre el que actúan todas las técnicas siguientes.
float y clear
float saca la imagen del flujo normal y la empuja a un costado, permitiendo que el texto la envuelva. Es la técnica clásica de las revistas.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flotado de imágenes</title>
<style>
body {
font-family: Georgia, "Times New Roman", serif;
background-color: #0f1115;
color: #e8eaed;
max-width: 760px;
margin: 0 auto;
padding: 24px;
line-height: 1.7;
}
img {
max-width: 100%;
height: auto;
border-radius: 8px;
}
.izquierda {
float: left;
width: 40%;
margin: 0 20px 12px 0;
}
.derecha {
float: right;
width: 40%;
margin: 0 0 12px 20px;
}
/* Corta el flotado: el contenido siguiente vuelve a empezar abajo */
.limpiar {
clear: both;
}
/* Centrado real: no se hace con float */
.centrada {
display: block;
margin-left: auto;
margin-right: auto;
width: 60%;
}
</style>
</head>
<body>
<h1>Imágenes flotadas y centradas</h1>
<img
class="izquierda"
src="mapa.jpg"
alt="Mapa del circuito con las curvas numeradas"
width="800"
height="600"
>
<p>
Este párrafo envuelve la imagen que está flotada a la izquierda. El texto
ocupa el espacio libre a la derecha de la imagen y baja línea por línea
hasta superar su altura, momento en el que recupera todo el ancho.
</p>
<div class="limpiar"></div>
<img
class="derecha"
src="mapa.jpg"
alt="Mapa del circuito con las curvas numeradas"
width="800"
height="600"
>
<p>
Este otro párrafo envuelve la imagen flotada a la derecha. La lógica es
idéntica, solo cambia el lado hacia el que se empuja la imagen y, en
consecuencia, el margen que hay que dejar para que el texto no quede pegado.
</p>
<div class="limpiar"></div>
<img
class="centrada"
src="mapa.jpg"
alt="Mapa del circuito con las curvas numeradas"
width="800"
height="600"
>
<p>
La imagen de arriba está centrada. No usa float, porque float no tiene un
valor de centrado: se convierte en bloque y se le dan márgenes automáticos
a ambos lados.
</p>
</body>
</html>
Los valores válidos de float son left, right, none, inline-start e inline-end. No existe float: center. Si lo escribes, el navegador descarta la declaración por inválida y la imagen se queda donde estaba, lo que da la impresión de que “el centrado no funciona”. Para centrar hay tres caminos claros:
/* 1. Bloque con margenes automaticos */
.centrada {
display: block;
margin-inline: auto;
}
/* 2. El contenedor centra el contenido en linea */
.contenedor {
text-align: center;
}
/* 3. Flexbox: centrado horizontal y vertical */
.contenedor-flex {
display: flex;
justify-content: center;
align-items: center;
min-height: 320px;
}
clear es el complemento de float: obliga a un elemento a colocarse por debajo de los elementos flotados previos. Sus valores son left, right, both y none. La técnica moderna evita el <div class="limpiar"> usando un contenedor que se contiene a sí mismo:
.articulo {
display: flow-root; /* el contenedor abarca a sus hijos flotados */
}
display: flow-root crea un contexto de formato de bloque nuevo, así que el contenedor “envuelve” a los elementos flotados sin necesidad de elementos extra ni del truco del pseudoelemento.
flowchart LR
A["Imagen en el flujo"] --> B{"Que necesito?"}
B -->|"Texto que la envuelva"| C["float: left o right<br/>mas margin del lado del texto"]
B -->|"Centrada, sola en su linea"| D["display: block<br/>mas margin-inline: auto"]
B -->|"Alineada con otros elementos"| E["display: flex en el padre<br/>mas justify-content"]
B -->|"Rejilla de varias imagenes"| F["display: grid en el padre<br/>mas grid-template-columns"]
C --> G["Cerrar con clear: both<br/>o display: flow-root en el padre"]
Galería con flexbox y con grid
Para colecciones de imágenes, las herramientas actuales son mucho más directas que float.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Galería flexible</title>
<style>
body {
font-family: system-ui, sans-serif;
background-color: #0d1117;
color: #e6edf3;
margin: 0;
padding: 24px;
}
.tira {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
}
.tira img {
flex: 0 1 200px;
height: 140px;
object-fit: cover;
border-radius: 8px;
}
.rejilla {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 12px;
margin-top: 32px;
}
.rejilla img {
width: 100%;
height: 160px;
object-fit: cover;
border-radius: 8px;
}
.rejilla img:first-child {
grid-column: span 2;
grid-row: span 2;
height: 100%;
}
</style>
</head>
<body>
<h1>Dos maneras de acomodar imágenes</h1>
<div class="tira">
<img src="p1.jpg" alt="Curva 1 del circuito" width="600" height="400">
<img src="p2.jpg" alt="Recta principal" width="600" height="400">
<img src="p3.jpg" alt="Zona de boxes" width="600" height="400">
<img src="p4.jpg" alt="Tribuna central" width="600" height="400">
</div>
<div class="rejilla">
<img src="p1.jpg" alt="Curva 1 del circuito" width="600" height="400">
<img src="p2.jpg" alt="Recta principal" width="600" height="400">
<img src="p3.jpg" alt="Zona de boxes" width="600" height="400">
<img src="p4.jpg" alt="Tribuna central" width="600" height="400">
<img src="p5.jpg" alt="Vista nocturna del circuito" width="600" height="400">
</div>
</body>
</html>
repeat(auto-fill, minmax(180px, 1fr)) es una de las expresiones más útiles de CSS Grid: crea tantas columnas como quepan, cada una de al menos 180 píxeles, repartiendo el sobrante en partes iguales. La galería se adapta sola sin escribir una sola media query.
Imágenes de fondo con CSS
Una imagen de fondo no es contenido del documento: es decoración de una caja. Se declara con background-image y se controla con una familia de propiedades relacionadas.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fondos con CSS</title>
<style>
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #ffffff;
background-color: #05070a;
}
.portada {
min-height: 70vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 32px;
background-image: url("imagenes/portada.jpg");
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
background-color: #1b2430; /* se ve mientras la imagen carga */
}
.patron {
min-height: 40vh;
padding: 32px;
background-image: url("imagenes/textura.png");
background-repeat: repeat;
background-size: 160px auto;
}
.franja-fija {
min-height: 60vh;
padding: 32px;
background-image: url("imagenes/montanas.jpg");
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
}
</style>
</head>
<body>
<header class="portada">
<h1>Super Club Net</h1>
<p>Todo lo que ves aquí atrás es una imagen de fondo.</p>
</header>
<section class="patron">
<h2>Patrón repetido</h2>
<p>La textura se repite en mosaico hasta cubrir toda la sección.</p>
</section>
<section class="franja-fija">
<h2>Fondo fijo</h2>
<p>Al desplazar la página, esta imagen se queda quieta.</p>
</section>
</body>
</html>
Las propiedades de fondo, una por una
| Propiedad | Valores frecuentes | Qué hace |
|---|---|---|
background-image | url("ruta.jpg"), linear-gradient(...), none | Define la imagen o el degradado de fondo |
background-repeat | repeat, no-repeat, repeat-x, repeat-y, space, round | Controla la repetición en mosaico |
background-size | auto, cover, contain, 320px auto, 50% 100% | Escala la imagen dentro de la caja |
background-position | center, top left, 50% 20%, right 20px bottom 10px | Ubica el punto de anclaje de la imagen |
background-attachment | scroll, fixed, local | Decide si el fondo se desplaza con la página |
background-color | Cualquier color | Color sólido detrás de la imagen |
background-clip | border-box, padding-box, content-box, text | Hasta dónde se pinta el fondo |
background-origin | border-box, padding-box, content-box | Desde dónde se posiciona la imagen |
background-blend-mode | multiply, overlay, screen, normal | Cómo se mezclan las capas de fondo entre sí |
Dos valores merecen aclaración porque se confunden todo el tiempo:
coverescala la imagen hasta cubrir toda la caja, aunque para lograrlo tenga que recortar los bordes. Nunca quedan huecos.containescala la imagen hasta que entre completa en la caja. Nunca recorta, pero puede dejar espacio vacío a los costados o arriba y abajo.
Es exactamente la misma distinción que en object-fit, aplicada a fondos en lugar de a elementos <img>.
La forma abreviada
Todas esas propiedades tienen una forma corta:
.portada {
background: #1b2430 url("imagenes/portada.jpg") no-repeat center / cover;
}
El orden dentro de la abreviatura es flexible, salvo por una regla: background-size va después de background-position y separado por una barra. Escribir center / cover sin la barra invalida toda la declaración.
Otra advertencia: la forma abreviada reinicia a su valor inicial todas las propiedades de fondo que no menciones. Si antes habías puesto background-attachment: fixed y luego declaras un background corto sin ese valor, el fixed desaparece.
Varias capas de fondo
background-image acepta una lista separada por comas. La primera capa queda arriba y las siguientes se apilan debajo.
.encabezado {
min-height: 60vh;
background-image:
linear-gradient(to bottom, rgba(5, 7, 10, 0.15), rgba(5, 7, 10, 0.85)),
url("imagenes/circuito.jpg");
background-repeat: no-repeat, no-repeat;
background-position: center, center;
background-size: cover, cover;
}
Ese degradado oscuro encima de la fotografía es la técnica estándar para que el texto blanco de una portada sea legible sin importar cómo sea la imagen. Nota que las demás propiedades también aceptan listas, y cada valor corresponde por posición a la capa del mismo índice.
Fondos sin archivo: degradados y patrones
CSS puede generar imágenes de fondo sin descargar nada:
.degradado-lineal {
background-image: linear-gradient(135deg, #1f6feb, #8957e5);
}
.degradado-radial {
background-image: radial-gradient(circle at 30% 30%, #f0b429, #7b3f00);
}
.degradado-conico {
background-image: conic-gradient(from 45deg, #e11d48, #f59e0b, #10b981, #e11d48);
}
/* Patron de rayas hecho solo con CSS */
.rayas {
background-image: repeating-linear-gradient(
45deg,
#1b222b 0 12px,
#232c38 12px 24px
);
}
Cero peticiones al servidor, cero kilobytes de imagen y escalado perfecto a cualquier tamaño. Para separadores, tramas y fondos sutiles es la opción más eficiente.
img frente a background-image: cuándo usar cada uno
| Criterio | <img> | background-image |
|---|---|---|
| Naturaleza | Contenido del documento | Decoración de una caja |
| Aparece en el DOM | Sí, es un elemento | No, es una propiedad de estilo |
| Texto alternativo | Sí, con alt | No existe equivalente |
| Lectores de pantalla | La anuncian y describen | La ignoran por completo |
| Imprimible por defecto | Sí | Muchos navegadores la omiten |
| Imágenes responsivas | srcset, sizes, <picture> | Media queries o image-set() |
| Recorte y encuadre | object-fit, object-position | background-size, background-position |
| Varias capas superpuestas | Requiere posicionamiento manual | Nativo, con lista separada por comas |
| Se puede enlazar directo | Sí, dentro de un <a> | No |
La regla práctica: si al quitar la imagen se pierde información, va en <img>; si solo se pierde estética, va en background-image. Una fotografía de producto es contenido. Una textura de papel detrás del texto es decoración.
Rutas: relativas frente a absolutas
Todo lo anterior depende de que el navegador encuentre el archivo. La ruta es la instrucción que le damos para lograrlo, y es donde más tiempo se pierde cuando algo no se ve.
Supongamos esta estructura de carpetas del proyecto:
flowchart TD
R["proyecto/"] --> I["index.html"]
R --> C["css/"]
R --> IMG["imagenes/"]
R --> B["blog/"]
C --> E["estilos.css"]
IMG --> L["logo.png"]
IMG --> F["fondo.jpg"]
IMG --> IC["iconos/"]
IC --> M["menu.svg"]
B --> A1["articulo.html"]
B --> BI["imagenes/"]
BI --> G["grafico.png"]
Rutas relativas
Una ruta relativa se resuelve tomando como punto de partida la ubicación del archivo que la contiene. Los símbolos son estos:
| Notación | Significado | Ejemplo desde index.html |
|---|---|---|
archivo.png | Mismo directorio | index.html y archivo.png están juntos |
./archivo.png | Mismo directorio, explícito | Equivale al anterior |
carpeta/archivo.png | Bajar un nivel | imagenes/logo.png |
carpeta/sub/archivo.png | Bajar dos niveles | imagenes/iconos/menu.svg |
../archivo.png | Subir un nivel | Desde blog/articulo.html, sube a la raíz |
../../archivo.png | Subir dos niveles | Se pueden encadenar sin límite |
Desde index.html (que está en la raíz del proyecto):
<img src="imagenes/logo.png" alt="Logotipo del club">
<img src="imagenes/iconos/menu.svg" alt="Abrir el menú">
<img src="blog/imagenes/grafico.png" alt="Gráfico de resultados">
Desde blog/articulo.html:
<!-- Subir un nivel y entrar a imagenes -->
<img src="../imagenes/logo.png" alt="Logotipo del club">
<!-- La carpeta imagenes que esta dentro de blog -->
<img src="imagenes/grafico.png" alt="Gráfico de resultados">
Fíjate en el detalle: imagenes/grafico.png escrito desde blog/articulo.html apunta a blog/imagenes/, no a la carpeta imagenes/ de la raíz. La misma cadena de texto significa archivos distintos según desde dónde se escriba. Esa es la esencia de “relativa”.
Rutas absolutas desde la raíz del sitio
Una ruta que empieza con una barra / no se resuelve contra el archivo actual, sino contra la raíz del sitio: el dominio.
<!-- Funciona igual desde cualquier archivo del sitio -->
<img src="/imagenes/logo.png" alt="Logotipo del club">
Si el sitio está en https://ejemplo.cl, esa ruta apunta a https://ejemplo.cl/imagenes/logo.png sin importar si la escribimos en index.html, en blog/articulo.html o en un archivo diez niveles más adentro. Es la opción más robusta para elementos compartidos como logotipos e íconos.
La contrapartida: al abrir el archivo HTML directamente desde el disco con doble clic (protocolo file://), la barra inicial apunta a la raíz del sistema de archivos, no a la carpeta del proyecto, y las imágenes no aparecen. Por eso conviene probar siempre con un servidor local en lugar de abrir el archivo a mano.
URLs absolutas completas
<img src="https://ejemplo.cl/imagenes/logo.png" alt="Logotipo del club">
Incluyen el esquema (https://) y el dominio. Se usan para imágenes alojadas en otro servidor. Traen dos consideraciones: dependes de la disponibilidad de ese servidor, y si el recurso desaparece o cambia, tu página se rompe sin que hayas tocado nada.
Rutas del sistema de archivos
<img src="/home/usuario/fotos/imagen.jpg" alt="Solo funciona en esta máquina">
Apuntar a una carpeta personal del disco funciona en la computadora donde se escribió y falla en cualquier otra. Es un error frecuente cuando se arrastra una imagen desde el explorador de archivos al editor.
El punto que casi nadie explica: rutas dentro de CSS
Esta es la trampa más común de todo el capítulo. Una ruta relativa dentro de un archivo .css se resuelve contra la ubicación del archivo CSS, no contra la del HTML.
Con la estructura del diagrama anterior, en css/estilos.css esto es correcto:
.portada {
background-image: url("../imagenes/fondo.jpg");
}
El ../ sube desde css/ hasta la raíz del proyecto y desde ahí entra a imagenes/. Si en cambio escribieras url("imagenes/fondo.jpg"), el navegador buscaría css/imagenes/fondo.jpg, que no existe.
Y si ese mismo CSS estuviera escrito dentro de una etiqueta <style> en index.html, la ruta correcta pasaría a ser url("imagenes/fondo.jpg"), porque ahora el punto de partida es el HTML. La misma declaración, dos rutas distintas según dónde viva.
flowchart TD
A["url( ... ) dentro de un estilo"] --> B{"Donde esta escrito<br/>ese estilo?"}
B -->|"En un archivo .css externo"| C["La base es la carpeta<br/>del archivo .css"]
B -->|"En una etiqueta style del HTML"| D["La base es la carpeta<br/>del documento HTML"]
B -->|"En un atributo style de un elemento"| D
C --> E["css/estilos.css necesita<br/>subir con ../ para llegar<br/>a imagenes/"]
D --> F["index.html llega directo<br/>con imagenes/"]
E --> G["Si la ruta empieza con /<br/>la base es siempre el dominio"]
F --> G
Las rutas que empiezan con / esquivan todo este problema, porque siempre se resuelven contra el dominio sin importar dónde estén escritas.
La etiqueta base
Existe un elemento que cambia el punto de partida de todas las rutas relativas del documento:
<head>
<base href="https://cdn.ejemplo.cl/assets/">
</head>
Con eso, un <img src="logo.png"> en cualquier parte del documento apunta a https://cdn.ejemplo.cl/assets/logo.png. Es una herramienta potente y a la vez peligrosa: afecta también a los enlaces <a href>, así que un <base> mal puesto rompe la navegación completa de la página. Solo debe haber uno y debe ir antes de cualquier otra referencia a un recurso.
data URI: la imagen dentro del documento
Una URL data: incrusta el contenido del archivo directamente en el atributo, codificado normalmente en base64.
<!-- Un cuadrado rojo de 1x1 pixel, en PNG, embebido -->
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
alt="Cuadrado rojo de un pixel"
width="1"
height="1"
>
También funciona en CSS, y con SVG queda especialmente legible porque no hace falta codificar en base64:
.check {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M2 8l4 4 8-8" stroke="%2310b981" stroke-width="2" fill="none"/></svg>');
background-repeat: no-repeat;
background-size: 16px 16px;
padding-left: 22px;
}
Dentro de un data: URI de SVG, el carácter # debe escribirse como %23; por eso el color aparece como %2310b981 en lugar de #10b981.
La estructura de un data URI es: data: + tipo MIME + ;base64 (si corresponde) + , + los datos.
| Ventaja | Costo |
|---|---|
| Cero peticiones adicionales al servidor | Base64 infla el tamaño alrededor de un 33 % |
| El documento es autocontenido | No se puede cachear por separado |
| Útil para íconos diminutos y marcadores | Ilegible en el código fuente |
| Evita el parpadeo inicial de íconos | Aumenta el peso del HTML o del CSS |
La recomendación práctica: reservarlo para archivos de menos de un par de kilobytes.
Ejemplo integrador: la página del club
Un documento completo que reúne casi todo lo visto: portada con fondo compuesto, logotipo con ruta relativa, galería en rejilla con object-fit, imagen flotada con texto envolvente y carga diferida.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Super Club Net — Galería</title>
<style>
:root {
--fondo: #0b0f14;
--superficie: #141b24;
--texto: #e8edf3;
--tenue: #93a4b8;
--acento: #38bdf8;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
background-color: var(--fondo);
color: var(--texto);
line-height: 1.7;
}
img {
display: block;
max-width: 100%;
height: auto;
}
.portada {
min-height: 65vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 40px 24px;
background-color: #101720;
background-image:
linear-gradient(to bottom, rgba(11, 15, 20, 0.25), rgba(11, 15, 20, 0.9)),
url("imagenes/circuito.jpg");
background-repeat: no-repeat, no-repeat;
background-position: center, center 30%;
background-size: cover, cover;
}
.portada img.logo {
width: 96px;
height: 96px;
margin-bottom: 16px;
}
.portada h1 {
font-size: clamp(2rem, 6vw, 3.5rem);
margin: 0 0 8px;
}
.portada p {
margin: 0;
color: var(--tenue);
max-width: 46ch;
}
main {
max-width: 960px;
margin: 0 auto;
padding: 48px 24px;
}
section {
margin-bottom: 56px;
display: flow-root;
}
h2 {
border-bottom: 2px solid var(--acento);
padding-bottom: 8px;
}
.destacada {
float: left;
width: 42%;
margin: 6px 24px 12px 0;
border-radius: 10px;
}
.rejilla {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 14px;
}
.rejilla figure {
margin: 0;
background-color: var(--superficie);
border-radius: 10px;
overflow: hidden;
}
.rejilla img {
width: 100%;
height: 150px;
object-fit: cover;
object-position: center 40%;
}
.rejilla figcaption {
padding: 10px 12px;
font-size: 0.88rem;
color: var(--tenue);
}
.cinta {
min-height: 220px;
border-radius: 12px;
background-image: repeating-linear-gradient(
45deg,
rgba(56, 189, 248, 0.10) 0 14px,
rgba(56, 189, 248, 0.02) 14px 28px
);
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
text-align: center;
color: var(--tenue);
}
@media (max-width: 620px) {
.destacada {
float: none;
width: 100%;
margin: 0 0 16px 0;
}
}
</style>
</head>
<body>
<header class="portada">
<img class="logo" src="imagenes/logo.svg" alt="" width="96" height="96">
<h1>Super Club Net</h1>
<p>Archivo fotográfico del circuito, temporada tras temporada.</p>
</header>
<main>
<section>
<h2>La curva peraltada</h2>
<img
class="destacada"
src="imagenes/peralte.jpg"
alt="Auto tomando la curva peraltada con el peralte inclinado hacia el interior"
width="900"
height="600"
>
<p>
El peralte de esta curva permite mantener velocidad sin depender por
completo del agarre lateral de los neumáticos. Es el punto donde más
diferencias se generan durante la vuelta rápida y, por lo mismo, el
lugar favorito de los fotógrafos.
</p>
<p>
La imagen está flotada a la izquierda y el texto la envuelve. En
pantallas angostas la media query cancela el flotado y la imagen pasa
a ocupar todo el ancho, porque envolver texto alrededor de una imagen
en un teléfono deja columnas de dos o tres palabras.
</p>
</section>
<section>
<h2>Galería de la temporada</h2>
<div class="rejilla">
<figure>
<img src="imagenes/g1.jpg" alt="Largada con los autos alineados en la grilla"
width="600" height="400" loading="lazy" decoding="async">
<figcaption>Largada</figcaption>
</figure>
<figure>
<img src="imagenes/g2.jpg" alt="Detención en boxes con el equipo cambiando neumáticos"
width="600" height="400" loading="lazy" decoding="async">
<figcaption>Boxes</figcaption>
</figure>
<figure>
<img src="imagenes/g3.jpg" alt="Vista nocturna del circuito con las luces encendidas"
width="600" height="400" loading="lazy" decoding="async">
<figcaption>Nocturna</figcaption>
</figure>
<figure>
<img src="imagenes/g4.jpg" alt="Público en la tribuna principal ondeando banderas"
width="600" height="400" loading="lazy" decoding="async">
<figcaption>Tribuna</figcaption>
</figure>
</div>
</section>
<section>
<h2>Fondo generado con CSS</h2>
<div class="cinta">
<p>Estas rayas diagonales no son un archivo: las dibuja repeating-linear-gradient.</p>
</div>
</section>
</main>
</body>
</html>
Nota que el logotipo lleva alt="". El nombre del club ya está en el <h1> justo debajo, así que repetirlo en el texto alternativo haría que un lector de pantalla lo anunciara dos veces seguidas.
Errores comunes
| Error | Causa | Solución |
|---|---|---|
| La imagen no aparece y sale el ícono roto | La ruta en src no coincide con la ubicación real del archivo | Abrir la pestaña de red de las herramientas del navegador, ver la URL exacta que se pidió y compararla con el árbol de carpetas |
| Funciona en tu máquina pero no al publicar | Diferencia de mayúsculas: Logo.PNG frente a logo.png | Los servidores Linux distinguen mayúsculas; usar siempre nombres en minúsculas y sin espacios |
| El fondo no carga desde el CSS externo | La ruta en url() se resolvió contra la carpeta del .css, no del HTML | Ajustar con ../ o usar una ruta absoluta que empiece con / |
float: center no centra nada | Ese valor no existe en la especificación de float | Usar display: block con margin-inline: auto, o text-align: center en el contenedor |
| El contenedor no crece con la imagen flotada dentro | Los elementos flotados salen del flujo y no aportan altura al padre | Añadir display: flow-root al contenedor, o un elemento con clear: both al final |
| La imagen se ve estirada o achatada | Se fijaron width y height con una proporción distinta a la original | Usar object-fit: cover o fijar solo una dimensión y dejar la otra en auto |
| Hay una franja de píxeles bajo la imagen | <img> es un elemento en línea y se apoya en la línea base del texto | Añadir display: block o vertical-align: middle |
| La página salta mientras carga | El navegador no reservó espacio porque no conocía el tamaño | Declarar width y height en el HTML y height: auto en CSS |
| El texto queda pegado a la imagen flotada | El flotado no aplica separación por sí mismo | Agregar margin del lado por donde corre el texto |
| El logotipo se ve borroso en pantallas de alta densidad | Se sirve un archivo con menos píxeles reales de los que la pantalla dibuja | Usar SVG para logotipos e íconos, o srcset con versiones de mayor resolución |
| La foto pesa varios megabytes | Se subió el archivo original de la cámara sin procesar | Redimensionar a las dimensiones de uso y convertir a WebP o AVIF |
| Un lector de pantalla lee el nombre del archivo | Falta el atributo alt | Escribir una descripción, o alt="" si la imagen es decorativa |
| Las imágenes de fondo no salen al imprimir | Muchos navegadores omiten los fondos en la impresión | Si el contenido debe imprimirse, usar <img> en lugar de background-image |
El background corto borró el attachment que había puesto antes | La forma abreviada reinicia todas las propiedades no mencionadas | Incluir todos los valores en la abreviatura o usar propiedades individuales |
background: center cover no aplica el tamaño | Falta la barra que separa posición de tamaño | Escribir center / cover |
El SVG del data: URI no se ve | El # del color rompe la URL | Reemplazar # por %23 dentro del data URI |
Herramientas del navegador para depurar imágenes
Cuando algo no aparece, tres pestañas de las herramientas de desarrollo resuelven casi todo:
- Red (Network): filtra por imágenes y muestra la URL exacta que el navegador pidió, el código de estado y el peso. Un
404confirma que la ruta está mal; un archivo de 4 MB confirma que hay que optimizar. - Elementos (Elements): al seleccionar el
<img>muestra sus dimensiones renderizadas y las dimensiones intrínsecas del archivo. Si el archivo mide 3000 píxeles y se muestra a 300, sobran datos descargados. - Consola: registra los errores de carga de recursos.
También se puede detectar por código cuando una imagen falla:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Detectar imágenes rotas</title>
</head>
<body>
<img id="foto" src="imagenes/no-existe.jpg" alt="Fotografía del circuito" width="600" height="400">
<p id="aviso"></p>
<script>
const foto = document.getElementById("foto");
const aviso = document.getElementById("aviso");
foto.addEventListener("error", function () {
aviso.textContent = "No se pudo cargar: " + foto.getAttribute("src");
foto.src = "imagenes/reemplazo.png";
});
foto.addEventListener("load", function () {
aviso.textContent =
"Cargada. Tamaño real: " +
foto.naturalWidth + " por " + foto.naturalHeight + " píxeles.";
});
</script>
</body>
</html>
Las propiedades naturalWidth y naturalHeight entregan las dimensiones reales del archivo, mientras que width y height (leídas desde JavaScript) entregan las dimensiones con las que se está mostrando. Comparar ambas es la forma más directa de descubrir imágenes sobredimensionadas. El evento error solo se dispara una vez por asignación de src; si la imagen de reemplazo también falla, el manejador se ejecutará de nuevo, así que conviene evitar un reemplazo que pueda entrar en bucle.
Buenas prácticas de rendimiento
| Práctica | Efecto |
|---|---|
| Servir la imagen a las dimensiones reales de uso | Evita descargar píxeles que nunca se ven |
| Convertir a WebP o AVIF con respaldo | Reduce entre un 25 % y un 50 % el peso |
| Usar SVG para logotipos, íconos y diagramas | Archivos diminutos y nitidez en cualquier pantalla |
Declarar width y height en el HTML | Elimina el salto de contenido durante la carga |
loading="lazy" fuera del área visible inicial | Ahorra descargas que quizá nunca se necesiten |
No aplicar lazy a la imagen principal | Evita retrasar justo lo que el usuario ya está viendo |
| Degradados y patrones con CSS en vez de archivos | Cero peticiones y escalado perfecto |
| Nombres de archivo en minúsculas, sin espacios ni tildes | Evita fallos al publicar en servidores Linux |
Un background-color de respaldo bajo cada fondo | Mantiene legible el texto mientras la imagen carga |
Ejercicios propuestos
-
Ficha de producto. Crea una página con una imagen de producto de 800 por 800 píxeles. Declara
widthyheighten el HTML, aplicamax-width: 100%yheight: autoen CSS, y escribe unaltque describa el producto para alguien que no puede verlo. Verifica en la pestaña de red que la imagen se descarga una sola vez. -
Comparador de object-fit. Toma una fotografía apaisada y muéstrala cinco veces dentro de cajas cuadradas de 240 por 240 píxeles, aplicando cada uno de los cinco valores de
object-fit. Añade un<figcaption>que explique lo que ocurre en cada caso. -
Artículo con imagen flotada. Escribe un artículo de al menos cuatro párrafos con una imagen flotada a la izquierda. Usa
display: flow-rooten el contenedor y comprueba que el borde inferior del contenedor queda por debajo de la imagen. Luego agrega una media query que cancele el flotado bajo los 600 píxeles de ancho. -
Portada con fondo compuesto. Construye un encabezado de altura
70vhcon dos capas de fondo: un degradado oscuro encima y una fotografía debajo. Ajustabackground-positionpara que el punto de interés de la foto quede visible tanto en pantalla ancha como angosta. -
Rutas cruzadas. Reproduce la estructura de carpetas del diagrama de este capítulo con archivos reales. Desde
blog/articulo.html, muestra el logotipo que está en la carpetaimagenes/de la raíz usando primero una ruta relativa y después una absoluta. Comprueba que ambas funcionan al servir el sitio y observa qué pasa al abrir el archivo con doble clic. -
La trampa del CSS externo. Mueve todos los estilos del ejercicio 4 a
css/estilos.csssin cambiar la ruta delurl(). Observa cómo el fondo desaparece, revisa la URL fallida en la pestaña de red y corrígela. Escribe en un comentario por qué la ruta anterior dejó de funcionar. -
Galería adaptable. Arma una galería de ocho imágenes con
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)),object-fit: coveryloading="lazy"en todas menos las dos primeras. Redimensiona la ventana y observa cómo cambia el número de columnas sin escribir media queries. -
Tres formatos, un mismo dibujo. Guarda un mismo logotipo en PNG, en WebP y en SVG. Anota el peso de cada archivo, muéstralos lado a lado a 400 píxeles de ancho y luego a 40 píxeles, y describe qué diferencias de nitidez aparecen.
-
Ícono embebido. Crea un
data:URI con un SVG sencillo (un círculo o una marca de verificación) y úsalo comobackground-imagede un botón. Recuerda codificar el#de los colores como%23. -
Auditoría de una página propia. Toma cualquier página que ya hayas hecho en este curso y revisa cada imagen: ¿tiene
alt?, ¿tienewidthyheight?, ¿el archivo pesa lo razonable?, ¿el formato es el adecuado para su contenido? Corrige lo que falte y anota el peso total antes y después.
Lo que viene
Ya podemos incrustar imágenes, elegir su formato, ubicarlas en la página, usarlas como fondo y apuntar correctamente a sus archivos. Ese último punto —la resolución de rutas— no era exclusivo de las imágenes: es el mismo mecanismo que gobierna hojas de estilo, scripts, videos y, sobre todo, enlaces.
En el capítulo 6 vamos a desarmar ese mecanismo pieza por pieza: qué es una URI y en qué se diferencia de una URL, cuáles son las partes de una dirección web (esquema, host, puerto, ruta, cadena de consulta y fragmento), cómo funcionan los enlaces internos hacia anclas de la misma página, cómo enlazar a correo o a teléfono, y qué hacen los atributos target y rel cuando abrimos un enlace en otra pestaña. El elemento <a> es el que convirtió a la web en una red, y le vamos a dedicar el capítulo completo.