Recortar y enmascarar desde CSS
clip-path y mask sobre elementos HTML: las formas básicas, el problema de escala de path(), la función shape(), y las diferencias de comportamiento respecto al uso desde SVG.
clip-path con formas básicas
La propiedad acepta un conjunto de funciones de forma, con coordenadas relativas a la caja de referencia del elemento:
.diagonal { clip-path: polygon(0 0, 100% 0, 100% 80%, 0 100%); }.circulo { clip-path: circle(50%); }.elipse { clip-path: ellipse(40% 50% at 30% 50%); }.recuadro { clip-path: inset(10px 20px round 8px); }.rectangulo{ clip-path: rect(0 100% 60% 0 round 12px); }.xywh { clip-path: xywh(0 0 100% 60% round 12px); }Los porcentajes se resuelven contra la caja de referencia, que por omisión es border-box y se puede cambiar añadiendo una palabra clave: clip-path: circle(50%) content-box.
circle() con un porcentaje merece una nota: el radio no se resuelve contra el ancho ni contra el alto, sino contra la diagonal normalizada de la caja de referencia, exactamente la misma fórmula que se explicó para el atributo r de un circle de SVG. En una caja no cuadrada, circle(50%) no es media anchura.
Estas funciones son interpolables entre sí si tienen la misma estructura, lo que permite transiciones:
.tarjeta { clip-path: inset(0); transition: clip-path 300ms ease; }.tarjeta:hover { clip-path: inset(0 0 0 0 round 24px); }Un polygon() transiciona con otro polygon() si tienen el mismo número de vértices. Es la restricción que obliga a escribir polígonos con vértices redundantes cuando se quiere animar entre formas de complejidad distinta.
Referenciar un clipPath de SVG
clip-path: url(#forma) funciona sobre un elemento HTML, apuntando a un clipPath que esté en un SVG del documento. Con una condición práctica importante: el clipPath tiene que estar en unidades adecuadas.
<svg width="0" height="0" style="position:absolute"> <clipPath id="estrella" clipPathUnits="objectBoundingBox"> <polygon points="0.5,0 0.61,0.35 1,0.35 0.68,0.57 0.79,0.91 0.5,0.7 0.21,0.91 0.32,0.57 0,0.35 0.39,0.35" /> </clipPath></svg>.foto { clip-path: url(#estrella); }Con objectBoundingBox el recorte se adapta al tamaño del elemento HTML. Con userSpaceOnUse las coordenadas serían píxeles absolutos y el recorte solo funcionaría para un tamaño concreto.
La referencia tiene que ser a un clipPath del mismo documento. Un url(recortes.svg#estrella) para clip-path no funciona en todos los motores; la referencia externa es fiable para mask y no lo es para clip-path.
El problema de path() y la solución shape()
clip-path: path("M 0 0 L 100 0 L 100 100 Z") acepta la sintaxis del atributo d, y trae una limitación grave: las coordenadas son píxeles absolutos y no escalan con el elemento. Un recorte diseñado para una tarjeta de 300 píxeles se queda pequeño en una de 600. No hay porcentajes, no hay unidades relativas, no hay nada.
La función shape() resuelve exactamente eso. Usa una sintaxis de comandos en palabras, y admite cualquier unidad de CSS, incluidos porcentajes y calc():
.hoja { clip-path: shape( from 0% 50%, curve to 50% 0% with 0% 0%, curve to 100% 50% with 100% 0%, line to 0% 50%, close );}Cada porcentaje se resuelve contra la caja de referencia, así que la forma escala. Es la función que hace que los recortes complejos sean usables en un diseño responsivo, y la que sustituye a path() en todo salvo en el caso de una forma de tamaño fijo.
Su disponibilidad no es universal: está en los motores basados en Chromium y en WebKit desde 2025, y no en todos. La degradación es dura (sin soporte, la declaración se descarta y no hay recorte), así que si la usas conviene una detección:
.hoja { clip-path: polygon(0 50%, 50% 0, 100% 50%); }@supports (clip-path: shape(from 0 0, close)) { .hoja { clip-path: shape(from 0% 50%, curve to 50% 0% with 0% 0%, curve to 100% 50% with 100% 0%, line to 0% 50%, close); }}mask desde CSS
La propiedad mask es una abreviatura con la misma estructura que background: imagen, posición, tamaño, repetición, origen, recorte y modo.
.lista { mask: linear-gradient(to bottom, #000 0 85%, transparent 100%);}.icono { background-color: currentColor; mask: url(/iconos/estrella.svg) center / contain no-repeat;}.foto { mask-image: url(#suave); /* un mask de SVG del documento */}Diferencias con SVG que hay que tener presentes:
El modo por omisión es alfa, no luminancia. Cuando la máscara es una imagen o un degradado de CSS, mask-mode vale match-source, que para una imagen significa alfa. Solo si la fuente es un mask de SVG se usa el modo que ese elemento declare. Esta inversión respecto a SVG es la causa de que un degradado a transparent funcione en CSS y uno a transparent en una máscara de SVG dé un resultado distinto.
Se puede componer varias máscaras. mask-composite con add, subtract, intersect y exclude permite combinar capas, algo que en SVG requiere filtros.
.marco { mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); mask-composite: exclude;}Ese patrón produce un marco: la máscara del contenido se resta de la total, dejando solo el borde. Es la técnica estándar para un borde con degradado.
El prefijo ya no hace falta. -webkit-mask fue necesario durante años y hoy la propiedad sin prefijo funciona en los tres motores. Si mantienes código antiguo verás las dos versiones; en código nuevo, sin prefijo.