Saltar al contenido

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.

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.

HTML
<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:

  • fill y fill-opacity: da igual de qué color pintes la forma del recorte.
  • stroke y todo lo que lo acompaña: el trazo no recorta. Una forma con fill="none" y un stroke grueso 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, mask y cualquier efecto.

Lo que sí se respeta:

  • clip-rule, que decide qué es interior cuando las subrutas se solapan.
  • transform sobre 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.

HTML
<!-- 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.

HTML
<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.

Recortar con texto

text está permitido dentro de un clipPath, y es el truco para rellenar letras con una imagen o un degradado:

HTML
<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.