Bordes borrosos en CSS | Programar Plus

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:

Captura de pantalla de un elemento con una imagen de fondo que muestra naranjas sobre una mesa de madera.  El borde de este elemento está borroso.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.

Captura de pantalla.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.

(Visited 7 times, 1 visits today)