clipPath: el recorte binario
Qué cuenta y qué se ignora dentro de un clipPath, por qué la opacidad no tiene efecto, el papel de clip-rule, y cómo el recorte cambia el área que responde al puntero.
Un clipPath responde a una pregunta binaria por cada punto: dentro o fuera. No hay medias tintas, no hay opacidad y no hay color; solo geometría. Esa restricción es lo que lo hace barato y predecible, y también lo que hace que la mitad de los intentos de usarlo fracasen, porque la gente pone dentro cosas que el recorte simplemente ignora.
- Escribir un
clipPathy aplicarlo a un elemento y a un grupo. - Enumerar qué se ignora del contenido de un
clipPathy por qué. - Combinar varias formas y usar
clip-rulepara producir un agujero. - Explicar cómo el recorte afecta al área sensible al puntero.
La forma básica
clipPath es un contenedor de formas. La unión de todas ellas define la región visible; todo lo que caiga fuera se descarta.
<svg viewBox="0 0 200 100" width="400">
<defs>
<clipPath id="ventana">
<circle cx="60" cy="50" r="40" />
<rect x="100" y="20" width="80" height="60" rx="10" />
</clipPath>
</defs>
<rect width="200" height="100" fill="url(#algo)" clip-path="url(#ventana)" />
</svg>
El rectángulo de fondo solo se ve dentro del círculo y dentro del rectángulo redondeado. La unión es implícita: no hay operadores booleanos, todas las formas del clipPath suman.
Se puede aplicar a un elemento suelto o a un g, y en el segundo caso recorta el subárbol entero después de componerlo. Eso importa: el recorte se aplica al resultado del grupo, no a cada hijo por separado.
clip-path es una propiedad, no solo un atributo, así que se puede poner desde CSS y transicionar entre valores compatibles.
Lo que se ignora dentro
Aquí está el malentendido central. Dentro de un clipPath, el motor solo mira la geometría. Se ignoran:
fillyfill-opacity: da igual de qué color pintes la forma del recorte.strokey todo lo que lo acompaña: el trazo no recorta. Una forma confill="none"y unstrokegrueso no produce un recorte con forma de anillo: produce un recorte con la forma del relleno, que es lo que la geometría encierra.opacity: una forma al 50 por ciento recorta igual que una al 100.filter,masky cualquier efecto.
Lo que sí se respeta:
clip-rule, que decide qué es interior cuando las subrutas se solapan.transformsobre las formas del recorte.display: none, que excluye esa forma del recorte.
Los únicos elementos permitidos dentro de un clipPath son las formas (path, rect, circle, ellipse, line, polyline, polygon), text, y use apuntando a alguno de ellos. Un g dentro de un clipPath no aporta nada: sus hijos no cuentan. Es un error frecuente al copiar un icono entero dentro de un recorte y ver que no recorta nada.
<!-- Mal: el g no aporta y el recorte queda vacio -->
<clipPath id="malo">
<g><path d="M0 0h50v50h-50z" /></g>
</clipPath>
<!-- Bien -->
<clipPath id="bueno">
<path d="M0 0h50v50h-50z" />
</clipPath>
Un clipPath vacío o cuyo contenido no aporta geometría recorta todo: el elemento desaparece por completo. Ese es el síntoma de un recorte mal construido, y es el mismo síntoma que un identificador equivocado, así que conviene comprobar los dos.
clip-rule y los agujeros
Con dos subrutas que se solapan, la pregunta de qué queda dentro la responde clip-rule, que funciona exactamente igual que fill-rule: nonzero por omisión, evenodd como alternativa.
<clipPath id="anillo" clip-rule="evenodd">
<path d="M 60 10 A 50 50 0 1 0 60 110 A 50 50 0 1 0 60 10 Z
M 60 40 A 20 20 0 1 0 60 80 A 20 20 0 1 0 60 40 Z" />
</clipPath>
Con evenodd, el círculo interior es un agujero y el recorte tiene forma de anillo. Con nonzero y ambas subrutas en el mismo sentido, no hay agujero.
Aquí está el detalle que hace perder tiempo: clip-rule se declara en las formas del recorte, no en el clipPath, aunque puesta en el clipPath funciona porque se hereda. Lo que no funciona es declararla en el elemento recortado: ese es el sitio donde la gente la pone y no tiene ningún efecto ahí.
Y una consecuencia importante: dos formas separadas dentro de un clipPath siempre se unen, nunca se restan, sin importar clip-rule. La regla de par e impar opera dentro de una sola ruta con varias subrutas. Para restar una forma de otra hay que meterlas en la misma d.
El recorte y el puntero
clip-path afecta al área que responde a los eventos de puntero: un clic fuera de la región recortada no alcanza al elemento. Eso es coherente (no puedes hacer clic en algo que no se ve) y es muy útil, porque convierte el recorte en una forma barata de definir zonas de interacción no rectangulares.
Es también la primera diferencia práctica con mask, que no afecta al hit testing: un elemento con una máscara totalmente opaca sigue recibiendo eventos en la zona invisible. Esa asimetría se explica en detalle en elegir entre recorte y máscara.
Un clipPath produce una máscara binaria con antialiasing en el borde: cada píxel del contorno se pinta con una cobertura parcial. Suena bien y produce un artefacto concreto.
Cuando dos elementos recortados con la misma forma se colocan uno al lado del otro (o uno recortado sobre un fondo del mismo color que su vecino), el borde compartido no suma 1. Cada uno aporta, digamos, un 60 por ciento de cobertura, y la suma compuesta con la fórmula habitual no da 120 sino algo menor que 1: aparece una línea fina más clara. Es el mismo fenómeno de costura de los patrones, y es la razón de que un mosaico de piezas recortadas muestre una retícula de líneas del color del fondo.
Peor todavía: cuando el elemento recortado tiene un trazo, el trazo se recorta también, y como el recorte pasa por el centro de la geometría, se queda con la mitad del grosor exactamente en el borde. Un icono con borde de 2 unidades recortado por su propia silueta sale con un borde de 1 unidad en el contorno y de 2 en el interior. La gente lo ve como «el borde se ve más fino en unos sitios» y busca la causa en el trazo.
Las salidas: dibujar el borde con un elemento aparte que no esté recortado, hacer la silueta ligeramente mayor que el recorte, o usar mask en lugar de clipPath cuando el borde suave es aceptable, porque la máscara reparte la transición en varios píxeles y disimula. Ninguna de las tres es gratis, y la elección depende de si el diseño exige un borde nítido o tolera uno suave.
Recortar con texto
text está permitido dentro de un clipPath, y es el truco para rellenar letras con una imagen o un degradado:
<svg viewBox="0 0 300 80" width="600">
<defs>
<clipPath id="letras">
<text x="10" y="60" font-size="64" font-weight="800"
font-family="system-ui, sans-serif">DIOS</text>
</clipPath>
<linearGradient id="fuego">
<stop offset="0" stop-color="#f9e2af" /><stop offset="1" stop-color="#f38ba8" />
</linearGradient>
</defs>
<rect width="300" height="80" fill="url(#fuego)" clip-path="url(#letras)" />
</svg>
Dos advertencias sobre esta técnica. La primera: el texto recortado sigue siendo texto en el DOM, así que se puede seleccionar y leer, pero está dentro de defs y por tanto no es visible para las tecnologías de asistencia como contenido; hay que dar el nombre accesible por otra vía. La segunda: la forma exacta depende de la fuente, y si la fuente no ha cargado cuando el recorte se calcula, el texto se recorta con la fuente de reserva y luego cambia. En un SVG servido como imagen, la fuente no carga en absoluto.
Recorta una fotografía con la silueta de un icono y comprueba las cuatro afirmaciones del artículo: que el fill del recorte da igual, que el stroke no recorta, que un g dentro no aporta nada, y que un clic fuera de la silueta no alcanza a la imagen. Después mete dos copias recortadas una al lado de otra y busca la costura del borde.