Desafortunadamente, clip-path: path () sigue siendo un No-Go | Programar Plus

Estaba extremadamente emocionado cuando escuché eso por primera vez. clip-path: path() venía a Firefox. ¡Imagínese poder codificar fácilmente un cuadro de respiración como el que se muestra a continuación con solo un elemento HTML y muy poco CSS sin necesidad de SVG o una gran lista de puntos dentro de la función de polígono!

Chris también estaba entusiasmado con la implementación inicial.

Qué divertido sería esto:

GIF animado.  Muestra un cuadrado que inhala y exhala: su cintura se contrae suavemente y luego se expande.Caja de respiración.

Decidí probarlo. Fui a CodePen, dejé caer un <div> en el panel HTML, le asignó dimensiones en unidades de ventana gráfica para que se adapte bien, agregó un background para que yo pudiera verlo. Luego fui a MDN para ver algunos ejemplos de uso … ¡y mi nube esponjosa de un sueño comenzó a colapsar!

Tenga en cuenta que clip-path: path() solo funciona en Firefox 63-70 con el layout.css.clip-path-path.enabled bandera establecida en true en about:config y en Firefox 71+ sin necesidad de habilitar ninguna bandera. (Fuente: MDN.)

Estos fueron los ejemplos que encontré:

path('M0 200L0 110A110 90 0 0 1 240 100L 200 340z')
path('M.5 1C.5 1 0 .7 0 .3A.25 .25 1 1 1 .5 .3 .25 .25 1 1 1 1 .3C1 .7 .5 1 .5 1Z')

¿Cuáles son esas coordenadas? La triste respuesta es valores de píxeles! Esos se utilizan porque el path() la función toma un SVG <path> cadena como un argumento que, como el valor del SVG d atributo en un <path> elemento: solo contiene un tipo de valor de coordenadas: píxeles sin unidad. En el caso de SVG, estos píxeles se escalan con el viewBox de El <svg> elemento pero no escalan en absoluto dentro del CSS path() ¡función!

Esto significa que el elemento siempre se recorta en la misma área fija si tenemos un elemento sensible con un path() valor para el clip-path propiedad. Por ejemplo, considere un cuadrado .box cuya longitud de borde es 35vw. Lo sujetamos a un corazón usando el path() función:

clip-path: path('M256 203C150 309 150 309 44 203 15 174 15 126 44 97 73 68 121 68 150 97 179 68 227 68 256 97 285 126 285 174 256 203')

Esta forma de corazón permanece del mismo tamaño mientras que las dimensiones de nuestro actual .box elemento cambia con la ventana gráfica:

GIF animado.  Muestra cómo el corazón recortado usando una ruta de píxel fija () no encaja dentro del rectángulo delimitador del elemento cuando el tamaño que depende de la ventana gráfica disminuye en tamaños de pantalla pequeños.El problema con un píxel fijo path().

Esta es una mala noticia aquí en 2020, donde el diseño receptivo es el estándar, no la excepción. Salvo por el extraño caso en el que el elemento que queremos recortar en realidad tiene un tamaño de píxel fijo, el path() ¡La función es completamente inútil! Todavía estamos mejor si usamos un SVG real hoy, o incluso un polygon() valor de aproximación para clip-path. En breve, path() todavía necesita mejoras, a pesar de haber despegado.

Amelia Bellamy-Royds ha sugirió dos posibilidades aquí:

Opción 1: Permitir calc() valores / unidades dentro path datos. Esto probablemente se haría al extender SVG path sintaxis en general.

Opción 2: especificar viewBox en clip-path declaración, escalar la ruta para ajustarla.

Yo personalmente prefiero la primera opción. La única ventaja que ofrece el segundo sobre el uso de SVG es el hecho de que no tenemos que incluir un SVG real. Dicho esto, incluir un SVG real siempre tendrá un mejor soporte.

Sin embargo, la primera opción podría ser una gran mejora con respecto al uso de SVG, al menos una mejora suficiente para justificar el uso clip-path en un elemento HTML en lugar de incluir un SVG en su interior. Consideremos el cuadro de respiración en la parte superior de esta publicación. Usando SVG, tenemos el siguiente marcado:

<svg viewBox='-75 -50 150 100'>
  <path/>
</svg>

Tenga en cuenta que el viewBox está configurado de tal manera que el 0,0 el punto está muerto en el medio. Esto significa que tenemos que hacer las coordenadas de la esquina superior izquierda (es decir, las dos primeras viewBox valores) igual a menos la mitad del viewBox dimensiones (es decir, las dos últimas viewBox valores).

En SCSS, establecemos la longitud del borde ($l) del cuadro cuadrado inicial como el más pequeño viewBox dimensión (que es el más pequeño de los dos últimos valores). Esto es 100 en nuestro caso.

Comenzamos el camino desde la esquina superior izquierda de nuestro cuadro cuadrado. Esto significa un mover a (M) hasta este punto, con coordenadas que son ambas iguales a menos la mitad de la longitud del borde.

Luego bajamos a la esquina inferior izquierda. Esto requiere dibujar una línea vertical con una longitud igual a la longitud del borde ($l) y desciende, en la dirección positiva del eje y. Entonces, usaremos el v mando.

A continuación, vamos a la esquina inferior derecha. Dibujaremos una línea horizontal con una longitud igual a la longitud del borde ($l) y va a la derecha, en la dirección positiva del eje x. Usaremos el h comando para que eso suceda.

Ir a la esquina superior derecha significa dibujar otra línea vertical de con una longitud igual a la longitud del borde ($l), por lo que usaremos el v comando de nuevo, solo que esta vez, la diferencia es el hecho de que vamos en la dirección opuesta del eje y, lo que significa que usamos las mismas coordenadas, pero con un signo menos.

Poniéndolo todo junto, tenemos el SCSS que nos permite crear el cuadro cuadrado inicial:

.box {
  d: path('M#{-.5*$l},#{-.5*$l} v#{$l} h#{$l} v#{-$l}');
  fill: darkorange
}

El CSS generado (donde $l es reemplazado por 100) Se ve como esto:

.box {
  d: path('M-50,-50 v100 h100 v-100');
  fill: darkorange;
}

El resultado se puede ver en la demostración interactiva a continuación, donde al pasar el cursor sobre una parte de los datos de la ruta se resalta la parte correspondiente en el SVG resultante y al revés:

Vea el lápiz de thebabydino (@thebabydino) en CodePen.

Sin embargo, si queremos que los bordes laterales respiren, no podemos utilizar líneas rectas. Reemplacemos esos con Bézier cuadrático (q) unos. El punto final sigue siendo el mismo, que tiene una longitud de borde hacia abajo a lo largo de la misma línea vertical. Viajamos por 0,#{$l} para llegar allí.

Pero, ¿qué pasa con el punto de control que debemos especificar antes de eso? Colocamos el punto verticalmente a mitad de camino entre los puntos inicial y final, lo que significa que bajamos a él la mitad de nuestro recorrido para llegar al punto final.

Y digamos que, horizontalmente, lo colocamos a un cuarto de largo de borde hacia un lado en una dirección u otra. Si queremos que las líneas sobresalgan para ensanchar la caja o apretarlas para estrecharla, necesitamos hacer algo como esto:

d: path('M#{-.5*$l},#{-.5*$l} 
         q#{-.25*$l},#{.5*$l} 0,#{$l} 
         h#{$l} 
         v#{-$l}'); /* swollen box */

d: path('M#{-.5*$l},#{-.5*$l} 
         q#{.25*$l},#{.5*$l} 0,#{$l} 
         h#{$l} 
         v#{-$l}'); /* squished box */

Esto se compila con el siguiente CSS:

d: path('M-50,-50 
         q-25,50 0,100 
         h100 
         v-100'); /* swollen box */

d: path('M-50,-50 
         q25,50 0,100 
         h100 
         v-100'); /* squished box */

La demostración interactiva a continuación muestra cómo esto path obras. Puede colocar el cursor sobre los componentes de datos de ruta para verlos resaltados en el gráfico SVG. También puede alternar entre las versiones hinchada y aplastada.

Vea el lápiz de thebabydino (@thebabydino) en CodePen.

Este es solo el borde izquierdo. También debemos hacer lo mismo con el borde derecho. La diferencia aquí es que vamos de la esquina inferior derecha a la esquina superior derecha, que está hacia arriba (en la dirección negativa del eje y). Colocaremos el punto de control fuera de la caja para obtener el efecto de buey ancho, lo que también significa colocarlo a la derecha de sus puntos finales (en la dirección positiva del eje x). Mientras tanto, colocaremos el punto de control adentro para obtener el efecto de caja estrecha, lo que significa colocarlo a la izquierda de sus puntos finales (en la dirección negativa del eje x).

d: path('M#{-.5*$l},#{-.5*$l} 
         q#{-.25*$l},#{.5*$l} 0,#{$l} 
         h#{$l} 
         q#{.25*$l},#{-.5*$l} 0,#{-$l}'); /* swollen box */

d: path('M#{-.5*$l},#{-.5*$l} 
         q#{.25*$l},#{.5*$l} 0,#{$l} 
         h#{$l} 
         q#{-.25*$l},#{-.5*$l} 0,#{-$l}'); /* squished box */

El SCSS anterior genera el CSS a continuación:

d: path('M-50,-50 
         q-25,50 0,100 
         h100 
         q25,-50 0,100'); /* swollen box */

d: path('M-50,-50 
         q25,50 0,100 
         h100 
         q-25,-50 0,-100'); /* squished box */

Vea el lápiz de thebabydino (@thebabydino) en CodePen.

Para obtener el efecto de respiración, animamos entre el estado hinchado y el estado aplastado:

.box {
  d: path('M#{-.5*$l},#{-.5*$l} 
           q#{-.25*$l},#{.5*$l} 0,#{$l} 
           h#{$l} 
           q#{.25*$l},#{-.5*$l} 0,#{-$l}'); /* swollen box */
  animation: breathe .5s ease-in-out infinite alternate
}

@keyframes breathe {
  to {
    d: path('M#{-.5*$l},#{-.5*$l} 
             q#{.25*$l},#{.5*$l} 0,#{$l} 
             h#{$l} 
             q#{-.25*$l},#{-.5*$l} 0,#{-$l}'); /* squished box */
  }
}

Dado que lo único que difiere entre los dos estados es el signo de la diferencia horizontal a los puntos de control (el signo del primer número después de la curva cuadrática de Bézier q comando), podemos simplificar las cosas con un mixin:

@mixin pdata($s: 1) {
  d: path('M#{-.5*$l},#{-.5*$l} 
           q#{-.25*$s*$l},#{.5*$l} 0,#{$l} 
           h#{$l} 
           q#{.25*$s*$l},#{-.5*$l} 0,#{-$l}')
}

.box {
  @include pdata();
  animation: breathe .5s ease-in-out infinite alternate
}

@keyframes breathe { to { @include pdata(-1) } }

Esto es más o menos lo que estoy haciendo para la demostración real de la caja de respiración, aunque el movimiento es un poco más discreto. Aún así, esto no hace absolutamente nada por el CSS generado; todavía tenemos dos rutas largas, feas y casi idénticas en el código compilado.

Sin embargo, si pudiéramos utilizar un <div>, recortado con un clip-path: path() que apoyaba todo tipo de valores, incluyendo calc() valores dentro, entonces podríamos hacer que el signo sea una propiedad personalizada --sgn, que luego podríamos animar entre -1 y 1 con la ayuda de Houdini.

div.box {
  width: 40vmin; height: 20vmin;
  background: darkorange;
  --sgn: 1;
  clip-path: path(M 25%,0%
                  q calc(var(--sgn)*-25%),50% 0,100%
                  h 50%
                  q calc(var(--sgn)*25%),-50% 0,-100%);
  animation: breathe .5s ease-in-out infinite alternate
}

@keyframes breathe { to { --sgn: -1 } }

Ser capaz de hacer esto marcaría una gran diferencia. Nuestro elemento se escalaría muy bien con la ventana gráfica y también lo haría el cuadro de respiración que recortamos de él. Y, lo más importante, no necesitaríamos repetir este trazado de recorte para obtener las dos versiones diferentes (la hinchada y la aplastada), porque al usar la propiedad personalizada del signo (--sgn) dentro de una calc() el valor haría el truco. Sin embargo, como está ahora, clip-path: path() es bastante inútil.