Digamos que queremos apuntar a un elemento y simplemente difuminar visualmente el borde del mismo. No existe una función de plataforma web simple e integrada a la que podamos acceder. Pero podemos hacerlo con un pequeño truco de CSS.
Esto es lo que buscamos:
El resultado deseado.
¡Veamos cómo podemos codificar este efecto, cómo podemos mejorarlo con esquinas redondeadas, extender el soporte para que funcione en todos los navegadores, qué traerá el futuro en este departamento y qué otros resultados interesantes podemos obtener a partir de la misma idea!
Codificación del borde difuminado básico
Comenzamos con un elemento en el que establecemos algunas dimensiones ficticias, una parte transparent
(apenas visible) border
y un background
cuyo tamaño es relativo al border-box
, pero cuya visibilidad restringimos a la padding-box
:
$b: 1.5em; // border-width
div {
border: solid $b rgba(#000, .2);
height: 50vmin;
max-width: 13em;
max-height: 7em;
background: url(oranges.jpg) 50%/ cover
border-box /* background-origin */
padding-box /* background-clip */;
}
La caja especificada por background-origin
es el cuadro cuya esquina superior izquierda es el 0 0
punto por background-position
y también la caja que background-size
(ajustado a cover
en nuestro caso) es relativo a. La caja especificada por background-clip
es la caja dentro de cuyos límites el background
es visible.
Los valores iniciales son padding-box
por background-origin
y border-box
por background-clip
, por lo que debemos especificar ambos en este caso.
Si necesita un repaso más profundo sobre background-origin
y background-clip
, puede consultar este artículo detallado sobre el tema.
El código anterior nos da el siguiente resultado:
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
A continuación, agregamos un pseudo-elemento absolutamente posicionado que cubre la totalidad de su padre border-box
y se coloca detrás (z-index: -1
). También hacemos que este pseudo-elemento herede la propiedad de su padre border
y background
, luego cambiamos el border-color
a transparent
y el background-clip
a border-box
:
$b: 1.5em; // border-width
div {
position: relative;
/* same styles as before */
&:before {
position: absolute;
z-index: -1;
/* go outside padding-box by
* a border-width ($b) in every direction */
top: -$b; right: -$b; bottom: -$b; left: -$b;
border: inherit;
border-color: transparent;
background: inherit;
background-clip: border-box;
content: ''
}
}
Ahora también podemos ver el background
detrás de lo apenas visible border
:
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
Muy bien, es posible que ya estés viendo hacia dónde se dirige esto. El siguiente paso es blur()
el pseudo-elemento. Dado que este pseudo-elemento solo es visible debajo del parcialmente transparente border
(el resto está cubierto por su padre padding-box
-restringido background
), resulta el border
El área es la única área de la imagen que vemos borrosa.
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
También hemos traído el alfa del elemento border-color
Abajo a .03
porque queremos que la borrosidad haga la mayor parte del trabajo de resaltar dónde border
es.
Esto puede parecer hecho, pero hay algo que todavía no me gusta: los bordes del pseudo-elemento ahora también están borrosos. ¡Así que arreglemos eso!
Una cosa conveniente cuando se trata del orden en que los navegadores aplican propiedades es que los filtros se aplican antes del recorte. Si bien esto no es lo que queremos y nos hace recurrir a soluciones inconvenientes en muchos otros casos … aquí mismo, ¡demuestra ser realmente útil!
Significa que, después de difuminar el pseudo-elemento, podemos recortarlo a su border-box
!
Mi forma preferida de hacer esto es configurando clip-path
a inset(0)
porque … ¡es la forma más sencilla de hacerlo, de verdad! polygon(0 0, 100% 0, 100% 100%, 0 100%)
sería una exageración.
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
En caso de que se esté preguntando por qué no configurar el clip-path
en el elemento real en lugar de establecerlo en el :before
pseudo-elemento, esto se debe a que la configuración clip-path
en el elemento lo convertiría en un contexto de apilamiento. Esto forzaría a todos sus elementos secundarios (y, en consecuencia, su borrosa :before
pseudo-elemento también) para estar contenido dentro de él y, por lo tanto, frente a su background
. Y luego no nuclear z-index
o !important
podría cambiar eso.
Podemos embellecer esto agregando un texto con un mejor font
, a box-shadow
y algunas propiedades de diseño.
¿Y si tenemos las esquinas redondeadas?
Lo mejor de usar inset()
en vez de polygon()
Para el clip-path
es eso inset()
también puede adaptarse a cualquier border-radius
¡podemos querer!
Y cuando digo alguna border-radius
, ¡Lo digo en serio! ¡Mira esto!
div {
--r: 15% 75px 35vh 13vw/ 3em 5rem 29vmin 12.5vmax;
border-radius: var(--r);
/* same styles as before */
&:before {
/* same styles as before */
border-radius: inherit;
clip-path: inset(0 round var(--r));
}
}
¡Funciona a las mil maravillas!
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
Ampliando el apoyo
Algunos navegadores móviles todavía necesitan -webkit-
prefijo para ambos filter
y clip-path
, así que asegúrese de incluir también esas versiones. Tenga en cuenta que están incluidos en las demostraciones de CodePen incrustadas aquí, aunque elegí omitirlos en el código presentado en el cuerpo de este artículo.
De acuerdo, pero ¿y si necesitamos ser compatibles con Edge? clip-path
no funciona en Edge, pero filter
lo hace, lo que significa que obtenemos el borde borroso, pero sin límites de corte nítidos.
Bueno, si no necesitamos redondear las esquinas, podemos usar el obsoleto clip
propiedad como alternativa. Esto significa agregar la siguiente línea justo antes del clip-path
unos:
clip: rect(0 100% 100% 0)
Y nuestra demostración ahora funciona en Edge … ¡más o menos! Los bordes derecho, inferior e izquierdo se cortan de forma nítida, pero el superior aún permanece borroso (solo en el modo de depuración del lápiz, todo parece estar bien para el iframe en la vista del editor). Y abrir DevTools o hacer clic derecho en la ventana de Edge o hacer clic en cualquier lugar fuera de esta ventana hace que el efecto de esta propiedad desaparezca. ¡Error del mes ahí mismo!
Muy bien, ya que esto es tan poco confiable y ni siquiera nos ayuda si queremos esquinas redondeadas, ¡probemos con otro enfoque!
Esto es un poco como rascarse detrás de la oreja izquierda con el pie derecho (o al revés, dependiendo de qué lado sea el más flexible), pero es la única forma en que puedo pensar para que funcione en Edge.
Es posible que algunos de ustedes ya hayan estado gritando en la pantalla algo como “pero Ana … overflow: hidden
! ” y sí, eso es lo que vamos a hacer ahora. Lo he evitado inicialmente por la forma en que funciona: corta todo el contenido descendiente fuera del padding-box
. No fuera del border-box
, como hemos hecho con el recorte.
Esto significa que tenemos que deshacernos de lo real border
y emularlo con padding
, que no me encanta porque puede dar lugar a más complicaciones, ¡pero vayamos paso a paso!
En lo que respecta a los cambios de código, lo primero que hacemos es eliminar todos border
-relacionadas con las propiedades y establecer el border-width
valor como el padding
. Luego establecemos overflow: hidden
y restringir el background
del elemento real a la content-box
. Finalmente, restablecemos el pseudo-elemento background-clip
al padding-box
valor y cero sus compensaciones.
$fake-b: 1.5em; // fake border-width
div {
/* same styles as before */
overflow: hidden;
padding: $fake-b;
background: url(oranges.jpg) 50%/ cover
padding-box /* background-origin */
content-box /* background-clip */;
&:before {
/* same styles as before */
top: 0; right: 0; bottom: 0; left: 0;
background: inherit;
background-clip: padding-box;
}
}
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
Si queremos esa superposición de “borde” apenas visible, necesitamos otra background
capa en el elemento real:
$fake-b: 1.5em; // fake border-width
$c: rgba(#000, .03);
div {
/* same styles as before */
overflow: hidden;
padding: $fake-b;
--img: url(oranges.jpg) 50%/ cover;
background: var(--img)
padding-box /* background-origin */
content-box /* background-clip */,
linear-gradient($c, $c);
&:before {
/* same styles as before */
top: 0; right: 0; bottom: 0; left: 0;
background: var(--img);
}
}
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
También podemos agregar esquinas redondeadas sin problemas:
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
Entonces, ¿por qué no hicimos esto desde el principio?
Recuerda cuando dije un poco antes que no usar un border
puede complicar las cosas más adelante?
Bueno, digamos que queremos tener un texto. Con el primer método, utilizando un border
y clip-path
, todo lo que se necesita para evitar que el contenido del texto toque el borroso border
está agregando un padding
(de digamos 1em
) en nuestro elemento.
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
Pero con el overflow: hidden
método, ya hemos utilizado el padding
propiedad para crear el “borde” borroso. Aumentar su valor no ayuda porque solo aumenta el ancho del borde falso.
Podríamos agregar el texto a un elemento secundario. O también podríamos usar el :after
pseudo-elemento!
La forma en que esto funciona es bastante similar al primer método, con el :after
reemplazando el elemento actual. La diferencia es que recortamos los bordes borrosos con overflow: hidden
en vez de clip-path: inset(0)
y el padding
en el elemento real son los pseudos ‘ border-width
($b
) más lo que sea padding
valor que queremos:
$b: 1.5em; // border-width
div {
overflow: hidden;
position: relative;
padding: calc(1em + #{$b});
/* prettifying styles */
&:before, &:after {
position: absolute;
z-index: -1; /* put them *behind* parent */
/* zero all offsets */
top: 0; right: 0; bottom: 0; left: 0;
border: solid $b rgba(#000, .03);
background: url(oranges.jpg) 50%/ cover
border-box /* background-origin */
padding-box /* background-clip */;
content: ''
}
&:before {
border-color: transparent;
background-clip: border-box;
filter: blur(9px);
}
}
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
¿Qué hay de tener texto y algunas esquinas redondeadas bastante extremas? Bueno, eso es algo que discutiremos en otro artículo, ¡estad atentos!
Qué pasa backdrop-filter
?
Algunos de ustedes se estarán preguntando (como yo cuando comencé a jugar con varias ideas para tratar de lograr este efecto) si backdrop-filter
no es una opción.
¡Pues sí y no!
Técnicamente, es posible obtener el mismo efecto, pero dado que Firefox aún no lo implementa, eliminaremos el soporte de Firefox si elegimos tomar esta ruta. Sin mencionar que este enfoque también nos obliga a usar ambos pseudo-elementos si queremos el mejor soporte posible para el caso en el que nuestro elemento tenga algún contenido de texto (lo que significa que necesitamos los pseudo-elementos y su padding-box
área background
para mostrar debajo de este texto).
Actualizar: debido a una regresión, el backdrop-filter
La técnica ya no funciona en Chrome, por lo que el soporte ahora se limita a Safari y Edge en el mejor de los casos.
Para aquellos que aún no saben qué backdrop-filter
hace: filtra lo que se puede ver a través del (parcialmente) transparent
partes del elemento sobre las que lo aplicamos.
La forma en que debemos hacerlo es la siguiente: ambos pseudo-elementos tienen un border
y un background
posicionado y dimensionado en relación con el padding-box
. Restringimos el background
de pseudo-elemento en la parte superior (el :after
) al padding-box
.
Ahora el :after
no tiene un background
en el border
área más y podemos ver a través de la :before
pseudo-elemento detrás de él allí. Establecemos un backdrop-filter
sobre el :after
y tal vez incluso cambiar eso border-color
desde transparent
ligeramente visible. El fondo (:before
) pseudoelementos background
que todavía es visible a través del (parcialmente) transparent
, apenas distinguible border
de El :after
anterior se vuelve borroso como resultado de aplicar el backdrop-filter
.
$b: 1.5em; // border-width
div {
overflow: hidden;
position: relative;
padding: calc(1em + #{$b});
/* prettifying styles */
&:before, &:after {
position: absolute;
z-index: -1; /* put them *behind* parent */
/* zero all offsets */
top: 0; right: 0; bottom: 0; left: 0;
border: solid $b transparent;
background: $url 50%/ cover
/* background-origin & -clip */
border-box;
content: ''
}
&:after {
border-color: rgba(#000, .03);
background-clip: padding-box;
backdrop-filter: blur(9px); /* no Firefox support */
}
}
Recuerde que la demostración en vivo para esto no funciona actualmente en Firefox y necesita el Características de la plataforma web experimental bandera habilitada en chrome://flags
para trabajar en Chrome.
Eliminando un pseudo-elemento
Esto es algo que no recomendaría hacer en la naturaleza porque también elimina el soporte de Edge, pero tenemos una forma de lograr el resultado que queremos con solo un pseudoelemento.
Comenzamos estableciendo el fondo de la imagen en el elemento (realmente no necesitamos establecer explícitamente un border
siempre que incluyamos su ancho en el padding
) y luego parcialmente transparent
, apenas visible background
en el pseudo-elemento absolutamente posicionado que cubre todo su padre. También configuramos el backdrop-filter
en este pseudo-elemento.
$b: 1.5em; // border-width
div {
position: relative;
padding: calc(1em + #{$b});
background: url(oranges.jpg) 50%/ cover;
/* prettifying styles */
&:before {
position: absolute;
/* zero all offsets */
top: 0; right: 0; bottom: 0; left: 0;
background: rgba(#000, .03);
backdrop-filter: blur(9px); /* no Firefox support */
content: ''
}
}
Muy bien, pero esto borra todo el elemento detrás del casi transparent
pseudoelemento, incluido su texto. Y no es ningún error, esto es lo que backdrop-filter
se supone que debe hacer.
El problema que nos ocupa.
Para solucionar este problema, debemos deshacernos de (no hacer transparent
, eso es completamente inútil en este caso) el rectángulo interior (cuyos bordes son una distancia $b
lejos de border-box
bordes) del pseudo-elemento.
Tenemos dos formas de hacer esto.
La primera forma (demostración en vivo) es con clip-path
y la técnica del túnel de ancho cero:
$b: 1.5em; // border-width
$o: calc(100% - #{$b});
div {
/* same styles as before */
&:before {
/* same styles as before */
/* doesn't work in Edge */
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%,
0 0,
#{$b $b}, #{$b $o}, #{$o $o}, #{$o $b},
#{$b $b});
}
}
La segunda forma (demostración en vivo) es con dos mask
capas (tenga en cuenta que, en este caso, necesitamos establecer explícitamente un border
en nuestro pseudo):
$b: 1.5em; // border-width
div {
/* same styles as before */
&:before {
/* same styles as before */
border: solid $b transparent;
/* doesn't work in Edge */
--fill: linear-gradient(red, red);
-webkit-mask: var(--fill) padding-box,
var(--fill);
-webkit-mask-composite: xor;
mask: var(--fill) padding-box exclude,
var(--fill);
}
}
Dado que ninguna de estas dos propiedades funciona en Edge, esto significa que el soporte ahora está limitado a los navegadores WebKit (y aún necesitamos habilitar la marca de características de la Plataforma Web Experimental para backdrop-filter
para trabajar en Chrome).
Solución futura (¡y mejor!)
El filter()
La función nos permite aplicar filtros en individuos background
capas. ¡Esto elimina la necesidad de un pseudo-elemento y reduce el código necesario para lograr este efecto a dos declaraciones CSS!
border: solid 1.5em rgba(#000, .03);
background: $url
border-box /* background-origin */
padding-box /* background-clip */,
filter($url, blur(9px))
/* background-origin & background-clip */
border-box
Como habrás adivinado, el problema aquí es el soporte. Safari es el único navegador que lo implementa en este momento, pero si cree que filter()
es algo que podría ayudarlo, puede agregar sus casos de uso y realizar un seguimiento del progreso de la implementación tanto para Chrome como para Firefox.
Más opciones de filtro de borde
Solo he hablado de difuminar el border
hasta ahora, pero esta técnica funciona para casi cualquier CSS filter
(ahorrar para drop-shadow()
que no tendría mucho sentido en este contexto). Puede jugar cambiando entre ellos y ajustando valores en la demostración interactiva a continuación:
Vea el lápiz de thebabydino (@thebabydino) en CodePen.
Y todo lo que hemos hecho hasta ahora ha utilizado solo uno filter
función, pero también podemos encadenarlos y luego las posibilidades son infinitas: ¿qué efectos geniales se le pueden ocurrir de esta manera?
Vea el lápiz de thebabydino (@thebabydino) en CodePen.