Saltar al contenido
clip-path-y-mascaras.css · devschool

Recortes y máscaras en CSS

Lección 28 de 50 · 13 min de lectura · Actualizado el

En esta lección
  1. clip-path: recortar con una forma
  2. clip-path no dibuja bordes ni sombras
  3. Animar un recorte
  4. Máscaras con mask-image
  5. clip-path o mask: cuál elegir
  6. shape-outside: que el texto rodee una forma
  7. Casos reales
  8. Accesibilidad y rendimiento
  9. Errores frecuentes
  10. Resumen

En filtros y efectos viste clip-path y mask de pasada, y en imágenes en CSS un primer recorte con polygon(). Aquí los estudiamos a fondo: las formas básicas, cómo escribir un polígono sin perderte, cómo animar un recorte, las máscaras con degradados, shape-outside para que el texto rodee una forma y algunos casos reales que te encontrarás en cualquier web.

La idea de fondo es sencilla. Un recorte (clip) decide dónde se ve el elemento, con bordes nítidos. Una máscara (mask) decide cuánto se ve cada punto, con transparencias graduales.

clip-path: recortar con una forma

clip-path define una región. Lo que queda dentro se ve; lo que queda fuera desaparece. Además, lo de fuera no recibe clics ni :hover. El elemento sigue ocupando su caja original en la maquetación: solo cambia lo que se pinta.

Las formas básicas son cuatro:

.a { clip-path: circle(40% at 50% 50%); }       /* radio y centro */
.b { clip-path: ellipse(50% 30% at 50% 50%); }  /* radio horizontal y vertical */
.c { clip-path: inset(10px 20px round 12px); }  /* margen recortado por cada lado */
.d { clip-path: polygon(50% 0, 100% 100%, 0 100%); }  /* tus propios vértices */

circle() y ellipse()

El radio puede ir en píxeles o en porcentaje. En circle(50%), el porcentaje se calcula sobre una referencia que mezcla ancho y alto, así que en una caja que no es cuadrada el tamaño no coincide con la mitad de ningún lado. Si quieres controlarlo, da el radio en píxeles o usa closest-side. Después de at va el centro: at 50% 50% es el medio y at 0 0, la esquina superior izquierda.

Un uso típico es un foco sobre una foto o un botón con forma de cápsula, aunque para un círculo perfecto sin más recorte border-radius: 50% es más simple.

inset(): un rectángulo recortado

inset(arriba derecha abajo izquierda) sigue la misma lógica que margin o padding: un valor para los cuatro lados, dos, tres o cuatro. Admite round para redondear las esquinas:

.foto { clip-path: inset(0 0 30% 0); }          /* corta el 30 % inferior */
.tarjeta { clip-path: inset(0 round 24px); }    /* esquinas redondeadas */

polygon(): la forma libre

polygon() recibe una lista de vértices, cada uno como un par x y, normalmente en porcentajes del elemento. El navegador los une en orden y cierra la figura. El origen 0 0 es la esquina superior izquierda y el 100% 100%, la inferior derecha.

Algunas formas que merece la pena conocer:

FormaCódigo
Triángulopolygon(50% 0, 100% 100%, 0 100%)
Rombopolygon(50% 0, 100% 50%, 50% 100%, 0 50%)
Flecha a la derechapolygon(0 0, 80% 0, 100% 50%, 80% 100%, 0 100%)
Corte diagonal abajopolygon(0 0, 100% 0, 100% 85%, 0 100%)
Trapeciopolygon(15% 0, 85% 0, 100% 100%, 0 100%)
Hexágonopolygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%)

Puedes mezclar unidades: polygon(0 0, 100% 0, 100% calc(100% - 40px), 0 100%) mantiene el corte inclinado en 40 px sea cual sea la altura. Es más estable que un porcentaje cuando el elemento cambia de tamaño.

Consejo: no calcules los puntos a ojo. En las DevTools de Chrome y Firefox, al pasar el ratón sobre el valor de clip-path verás la forma encima del elemento y podrás arrastrar los vértices. Cuando te guste, copia el valor al CSS.

Un ejemplo real: cabecera con corte inclinado

.cabecera {
  min-height: 320px;
  background: url("img/sierra.jpg") center / cover;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 3rem), 0 100%);
  padding-bottom: 3rem;   /* que el texto no caiga en la zona recortada */
}

La cabecera sigue siendo una caja rectangular, pero su borde inferior queda inclinado. Recuerda que lo recortado sigue ocupando espacio: el contenido de la siguiente sección empieza donde terminaba la caja original. Si quieres que suba y tape el hueco, usa un margin-top negativo en la siguiente sección.

clip-path no dibuja bordes ni sombras

Esto sorprende a casi todo el mundo: un elemento recortado pierde su border y su box-shadow fuera de la forma, porque también se recortan. Para tener un borde o una sombra sobre una forma recortada tienes dos caminos:

  • Sombra: pon filter: drop-shadow(...) en el contenedor del elemento recortado. El filtro se aplica a lo que se ve, ya recortado.
  • Borde: apila dos elementos con la misma forma, uno más grande de color de borde y otro más pequeño encima, ambos con clip-path.
.contenedor { filter: drop-shadow(0 6px 8px rgb(0 0 0 / 0.3)); }
.contenedor .insignia { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }

Ya lo vimos en la lección de filtros: el filtro va en el padre, o el recorte se llevará la sombra por delante.

Animar un recorte

clip-path se puede animar con transition o @keyframes, igual que cualquier propiedad con valores intermedios (lo tienes explicado en transiciones y animaciones). Hay una regla de oro: ambos extremos deben usar la misma función y, en un polígono, el mismo número de vértices. Si no, el navegador no sabe emparejarlos y salta de un estado al otro sin transición.

Revelar una imagen

El efecto más usado es “descubrir” un elemento desde un lado:

.foto {
  clip-path: inset(0 100% 0 0);     /* todo recortado por la derecha */
  transition: clip-path 0.6s ease;
}
.foto.visible {
  clip-path: inset(0);              /* completamente visible */
}

Se activa añadiendo la clase visible desde JavaScript cuando el elemento entra en pantalla, o con :hover.

De una forma a otra

.insignia {
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);     /* rombo: 4 vértices */
  transition: clip-path 0.4s ease;
}
.insignia:hover {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);       /* cuadrado: 4 vértices */
}

Si quieres pasar de un triángulo (3 puntos) a un cuadrado (4), repite un vértice en el triángulo para que ambos tengan cuatro:

.a { clip-path: polygon(50% 0, 50% 0, 100% 100%, 0 100%); }   /* triángulo con un punto repetido */
.a:hover { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }

Con @keyframes

@keyframes respirar {
  from { clip-path: circle(30% at 50% 50%); }
  to   { clip-path: circle(60% at 50% 50%); }
}
.foco { animation: respirar 2s ease-in-out infinite alternate; }

Un recorte animado suele ser barato para el navegador en comparación con un blur(), pero conviene probarlo en móviles modestos. Y, como siempre, respeta a quien prefiere menos movimiento: envuelve las animaciones decorativas en @media (prefers-reduced-motion: no-preference).

Máscaras con mask-image

Una máscara usa una imagen (o un degradado) para decidir la opacidad de cada punto. Donde la imagen de la máscara es opaca, el elemento se ve completo; donde es transparente, se oculta; en los valores intermedios, se ve a medias. Lo que cuenta es el canal de transparencia: black, red o white valen lo mismo si son opacos.

Degradados como máscara

Es el uso más práctico. Un elemento que se desvanece por un borde:

/* Difuminar el final de un texto largo: pista de que hay más */
.extracto {
  max-height: 7rem;
  overflow: hidden;
  mask-image: linear-gradient(to bottom, black 55%, transparent);
}

/* Una galería con scroll horizontal que se desvanece en ambos lados */
.carrusel {
  overflow-x: auto;
  mask-image: linear-gradient(to right, transparent, black 8%, black 92%, transparent);
}

Lee el degradado como una curva de visibilidad: en black 55%, transparent, hasta el 55 % de la altura todo se ve, y desde ahí se desvanece hasta desaparecer del todo al final.

Con un radial-gradient consigues un foco que difumina los bordes de una foto, ideal para una portada que se funde con el fondo:

.portada {
  mask-image: radial-gradient(ellipse at center, black 40%, transparent 72%);
}

Imagen o SVG como máscara

Con una imagen con transparencia (PNG o SVG) el elemento toma su silueta. Es la forma estándar de colorear iconos con CSS: el icono pone la forma y background pone el color.

.icono {
  display: inline-block;
  width: 1.5rem;
  aspect-ratio: 1;
  background: currentColor;                         /* hereda el color del texto */
  mask: url("img/montana.svg") center / contain no-repeat;
}
.enlace:hover .icono { background: #f97316; }

La forma abreviada mask se parece a background: imagen, posición, tamaño, repetición. Tiene también mask-size, mask-position, mask-repeat o mask-composite para combinar varias máscaras. Los navegadores actuales la aceptan sin prefijo; en código antiguo verás -webkit-mask-image.

Nota: la imagen de la máscara se carga como cualquier recurso. Si está en otro dominio, necesita permisos CORS. Lo más cómodo es servirla desde tu propia web.

clip-path o mask: cuál elegir

clip-pathmask
BordesNítidosPueden ser suaves
Se define conFormas geométricasImágenes y degradados
Transparencia parcialNoSí
Zona ocultaNo recibe clicsPuede seguir recibiendo clics aunque no se vea
Ideal paraCortes diagonales, formas, reveladosDesvanecidos, iconos con color, siluetas complejas

Si necesitas “cortar y punto”, clip-path. Si necesitas “desvanecer” o seguir la silueta de un dibujo, mask.

shape-outside: que el texto rodee una forma

Hasta ahora hemos recortado lo que se ve. shape-outside hace otra cosa: cambia la forma alrededor de la cual fluye el texto. Solo funciona en elementos con float. Normalmente el texto rodea la caja rectangular de la imagen; con shape-outside, rodea su forma real:

.foto-circular {
  float: left;
  width: 160px;
  height: 160px;
  margin-right: 16px;
  border-radius: 50%;           /* lo que se ve */
  shape-outside: circle(50%);   /* por donde pasa el texto */
}

Sin shape-outside, el texto dejaría un hueco cuadrado en las esquinas redondeadas. Con ella, las líneas se acortan siguiendo la curva y la maquetación parece de revista. Admite las mismas formas que clip-path (circle(), ellipse(), inset(), polygon()) y también una imagen: shape-outside: url("img/silueta.png") hace que el texto siga la parte opaca del PNG.

Dos detalles útiles. Con shape-margin: 12px separas el texto de la forma sin tocar el margen del elemento. Y recuerda que shape-outside no recorta nada: si quieres que el elemento se vea con esa forma, necesitas border-radius o clip-path además.

Casos reales

Algunas aplicaciones que verás una y otra vez:

  • Foto de perfil circular con border-radius: 50% (o clip-path: circle() si necesitas otra forma).
  • Cabecera con borde inclinado con polygon(), como la de más arriba.
  • Botones y etiquetas con forma de flecha o de insignia, con polygon() y filter: drop-shadow() en el contenedor.
  • Revelado al hacer scroll de imágenes o títulos con inset() animado.
  • Texto largo que se desvanece con una máscara degradada y un botón “Leer más”.
  • Iconos del color del texto con mask, sin necesidad de un archivo por cada color.
  • Listas con scroll que se desvanecen en los extremos para indicar que hay más contenido.
  • Foco o linterna con circle() animado que sigue al ratón: la posición se cambia con una variable CSS desde JavaScript.

Un último truco: para el efecto “linterna” actualizas dos variables desde JavaScript y el CSS hace el resto:

.mapa { clip-path: circle(90px at var(--x, 50%) var(--y, 50%)); }
const mapa = document.querySelector('.mapa');
mapa.addEventListener('pointermove', (e) => {
  const caja = mapa.getBoundingClientRect();
  mapa.style.setProperty('--x', `${e.clientX - caja.left}px`);
  mapa.style.setProperty('--y', `${e.clientY - caja.top}px`);
});

Accesibilidad y rendimiento

  • Recortar no es ocultar. Un elemento recortado sigue en el árbol de accesibilidad: un lector de pantalla lo leerá aunque no se vea. Si debe esconderse del todo, usa hidden o display: none.
  • No recortes contenido importante. Un texto cortado por un polígono puede perder letras. Deja margen interior.
  • Cuidado con el foco. Si recortas un botón y su contorno de foco queda fuera de la forma, desaparecerá. Revisa con el teclado.
  • Contexto de apilamiento. clip-path y mask crean uno nuevo, como opacity o transform; si un menú se va por debajo de algo, mira si un ancestro tiene alguno de ellos (más en z-index y capas).
  • Rendimiento. Una máscara grande y animada sobre una foto pesada puede ir a tirones en móviles. Anima el recorte de elementos pequeños y mide.

Errores frecuentes

  • Animar polígonos con distinto número de vértices. No hay transición: salta. Repite vértices hasta igualarlos.
  • Esperar un borde o una sombra en un elemento recortado. Se recortan con él. Usa drop-shadow() en el contenedor.
  • Creer que el recorte libera el espacio. La caja original sigue ocupando su sitio en la maquetación.
  • Usar shape-outside sin float. No hace nada: solo actúa sobre elementos flotantes.
  • Pensar en colores en una máscara. Solo importa la transparencia; un degradado entre dos colores opacos no oculta nada.
  • Olvidar el espacio para el texto. Un texto cerca del borde de un polígono se corta. Añade padding.
  • Recortar un elemento interactivo y dejar sin zona clicable lo que no se ve. Es lo esperable, pero comprueba que la parte visible sigue siendo cómoda de pulsar.

Resumen

HerramientaPara qué sirveEjemplo
clip-path: circle() / ellipse()Recorte redondocircle(40% at 50% 50%)
clip-path: inset()Rectángulo recortado y reveladosinset(0 100% 0 0)
clip-path: polygon()Formas libres y cortes diagonalespolygon(0 0, 100% 0, 100% 85%, 0 100%)
Animar clip-pathRevelar o transformar la formaMismos vértices en los dos extremos
filter: drop-shadow()Sombra de una forma recortadaEn el contenedor
mask-imageDesvanecer o dar siluetalinear-gradient(black, transparent)
mask con SVGIconos con el color del textobackground: currentColor
shape-outsideEl texto rodea una formaCon float y border-radius

En la siguiente lección pasamos a contenidos más cotidianos: listas y tablas.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

clip-path-y-mascaras.html
Resultado

Pon a prueba lo que has aprendido

[CSS] Quieres animar un clip-path al pasar el ratón. ¿Qué pasa con este código?
.forma {
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  transition: clip-path 0.4s;
}
.forma:hover {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}

[CSS] ¿Qué efecto produce esta máscara sobre un texto largo?
.extracto {
  max-height: 7rem;
  overflow: hidden;
  mask-image: linear-gradient(to bottom, black 60%, transparent);
}

[CSS] Has recortado una insignia con clip-path y su box-shadow no se ve. ¿Cómo consigues una sombra que siga la forma recortada?

[CSS] ¿Qué hace falta para que shape-outside haga que el texto rodee una forma circular?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.