xlera8

Un elegante efecto de desplazamiento para tu avatar

¿Conoces ese tipo de efecto en el que la cabeza de alguien se asoma por un círculo o un agujero? La famosa animación de Porky Pig en la que se despide con la mano mientras salta de una serie de anillos rojos es el ejemplo perfecto, y Kilian Valkhof en realidad lo recreó aquí en CSS-Tricks hace un tiempo.

Tengo una idea similar pero abordada de una manera diferente y con una pizca de animación. Creo que es bastante práctico y crea un buen efecto de desplazamiento que puedes usar en algo como tu propio avatar.

¿Mira eso? Vamos a hacer una animación de escala en la que el avatar parece salirse del círculo en el que se encuentra. Genial, ¿verdad? No mires el código y construyamos esta animación juntos paso a paso.

El HTML: solo un elemento

Si no has comprobado el código de la demo y te preguntas cuántos divs esto tomará, luego deténgase allí, porque nuestro marcado no es más que un elemento de imagen único:

<img src="" alt="">

¡Sí, un solo elemento! La parte desafiante de este ejercicio es usar la menor cantidad de código posible. si has estado siguiéndome por un tiempo, deberías estar acostumbrado a esto. Me esfuerzo por encontrar soluciones CSS que se puedan lograr con el código más pequeño y fácil de mantener posible.

Yo escribí una serie de artículos aquí en CSS-Tricks donde exploro diferentes efectos de desplazamiento utilizando el mismo marcado HTML que contiene un solo elemento. Entro en detalles sobre gradientes, máscaras, recortes, contornos e incluso técnicas de diseño. Recomiendo encarecidamente revisarlos porque reutilizaré muchos de los trucos en esta publicación.

Un archivo de imagen que sea cuadrado con un fondo transparente funcionará mejor para lo que estamos haciendo. Aquí está el que estoy usando si quieres empezar con eso.

Diseñado por Cang

Espero ver muchos ejemplos de esto como sea posible usando imágenes reales, así que por favor comparte tu resultado final en los comentarios cuando hayas terminado para que podamos crear una colección.

Antes de saltar a CSS, primero analicemos el efecto. La imagen se hace más grande al pasar el mouse, por lo que seguramente usaremos transform: scale() ahí. Hay un círculo detrás del avatar, y un degradado radial debería funcionar. Finalmente, necesitamos una forma de crear un borde en la parte inferior del círculo que cree la apariencia del avatar detrás del círculo.

¡Pongámonos a trabajar!

El efecto de escala

Comencemos agregando la transformación:

img { width: 280px; aspect-ratio: 1; cursor: pointer; transition: .5s;
}
img:hover { transform: scale(1.35);
}

Nada complicado todavía, ¿verdad? Vamonos.

El círculo

Dijimos que el fondo sería un degradado radial. Eso es perfecto porque podemos crear paradas duras entre los colores de un degradado radial, lo que hace que parezca que estamos dibujando un círculo con líneas continuas.

img { --b: 5px; /* border width */ width: 280px; aspect-ratio: 1; background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), #C02942 calc(100% - var(--b)) 99%, #0000 ); cursor: pointer; transition: .5s;
}
img:hover { transform: scale(1.35);
}

Tenga en cuenta la variable CSS, --b, estoy usando allí. Representa el grosor del "borde" que en realidad solo se usa para definir las paradas de color duro para la parte roja del degradado radial.

El siguiente paso es jugar con el tamaño del degradado al pasar el mouse. El círculo debe mantener su tamaño a medida que crece la imagen. Ya que estamos aplicando un scale() transformación, en realidad necesitamos disminuir el tamaño del círculo porque de lo contrario se amplía con el avatar. Entonces, mientras la imagen se amplía, necesitamos que el degradado se reduzca.

Comencemos definiendo una variable CSS, --f, que define el "factor de escala", y utilícelo para establecer el tamaño del círculo. Estoy usando 1 como el valor predeterminado, ya que esa es la escala inicial de la imagen y el círculo del que nos transformamos.

Aquí hay una demostración para ilustrar el truco. Pase el cursor para ver lo que sucede detrás de escena:

Agregué un tercer color al radial-gradient para identificar mejor el área del gradiente al pasar el mouse:

radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), #C02942 calc(100% - var(--b)) 99%, lightblue
);

Ahora tenemos que colocar nuestro fondo en el centro del círculo y asegurarnos de que ocupe toda la altura. Me gusta declarar todo directamente en el background propiedad abreviada, por lo que podemos agregar nuestro posicionamiento de fondo y asegurarnos de que no se repita agregando esos valores justo después de la radial-gradient():

background: radial-gradient() 50% / calc(100% / var(--f)) 100% no-repeat;

El fondo se coloca en el centro (50%), tiene un ancho igual a calc(100%/var(--f)), y tiene una altura igual a 100%.

Nada escala cuando --f es igual a 1 — de nuevo, nuestra escala inicial. Mientras tanto, el degradado ocupa todo el ancho del contenedor. cuando aumentamos --f, el tamaño del elemento crece gracias a la scale() transform — y el tamaño del degradado disminuye.

Esto es lo que obtenemos cuando aplicamos todo esto a nuestra demostración:

¡Nos estamos acercando! Tenemos el efecto de desbordamiento en la parte superior, pero aún necesitamos ocultar la parte inferior de la imagen, para que parezca que está saliendo del círculo en lugar de sentarse frente a él. Esa es la parte complicada de todo esto y es lo que vamos a hacer a continuación.

el borde inferior

Primero traté de abordar esto con el border-bottom propiedad, pero no pude encontrar una manera de hacer coincidir el tamaño del borde con el tamaño del círculo. Esto es lo mejor que pude obtener y puede ver de inmediato que está mal:

La solución real es usar el outline propiedad. Sí, outlineno, border. En un artículo anterior, muestro como outline es poderoso y nos permite crear efectos de desplazamiento geniales. Combinado con outline-offset, tenemos exactamente lo que necesitamos para nuestro efecto.

La idea es establecer un outline en la imagen y ajuste su desplazamiento para crear el borde inferior. El desplazamiento dependerá del factor de escala de la misma manera que lo hizo el tamaño del degradado.

Ahora tenemos nuestro "borde" inferior (en realidad, un outline) combinado con el "borde" creado por el degradado para crear un círculo completo. Todavía tenemos que ocultar partes de la outline (desde la parte superior y los lados), a lo que llegaremos en un momento.

Aquí está nuestro código hasta ahora, que incluye un par de variables CSS más que puede usar para configurar el tamaño de la imagen (--s) y el color del “borde” (--c):

img { --s: 280px; /* image size */ --b: 5px; /* border thickness */ --c: #C02942; /* border color */ --f: 1; /* initial scale */ width: var(--s); aspect-ratio: 1; cursor: pointer; border-radius: 0 0 999px 999px; outline: var(--b) solid var(--c); outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b)); background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), var(--c) calc(100% - var(--b)) 99%, #0000 ) 50% / calc(100% / var(--f)) 100% no-repeat; transform: scale(var(--f)); transition: .5s;
}
img:hover { --f: 1.35; /* hover scale */
}

Como necesitamos un borde inferior circular, agregamos un border-radius en la parte inferior, lo que permite outline para que coincida con la curvatura del gradiente.

El cálculo utilizado en outline-offset es mucho más sencillo de lo que parece. Por defecto, outline es dibujado afuera de la caja del elemento. Y en nuestro caso, lo necesitamos para superposición el elemento. Más precisamente, necesitamos que siga el círculo creado por el degradado.

Diagrama de la transición de fondo.

Cuando escalamos el elemento, vemos el espacio entre el círculo y el borde. No olvidemos que la idea es mantener el círculo del mismo tamaño después de que se ejecute la transformación de escala, lo que nos deja el espacio que usaremos para definir el desplazamiento del contorno como se ilustra en la figura anterior.

No olvidemos que el segundo elemento está escalado, por lo que nuestro resultado también está escalado... lo que significa que debemos dividir el resultado por f para obtener el valor de compensación real:

Offset = ((f - 1) * S/2) / f = (1 - 1/f) * S/2

Agregamos un signo negativo ya que necesitamos que el contorno vaya de afuera hacia adentro:

Offset = (1/f - 1) * S/2

Aquí hay una demostración rápida que muestra cómo el contorno sigue el degradado:

Es posible que ya lo vea, pero aún necesitamos que el contorno inferior se superponga al círculo en lugar de dejar que se desangre a través de él. Podemos hacerlo eliminando el tamaño del borde del desplazamiento:

outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2) - var(--b));

Ahora necesitamos encontrar cómo eliminar la parte superior del contorno. En otras palabras, solo queremos la parte inferior de la imagen. outline.

Primero, agreguemos espacio en la parte superior con relleno para ayudar a evitar la superposición en la parte superior:

img { --s: 280px; /* image size */ --b: 5px; /* border thickness */ --c: #C02942; /* border color */ --f: 1; /* initial scale */ width: var(--s); aspect-ratio: 1; padding-block-start: calc(var(--s)/5); /* etc. */
}
img:hover { --f: 1.35; /* hover scale */
}

No hay una lógica particular para ese relleno superior. La idea es asegurarse de que el contorno no toque la cabeza del avatar. Usé el tamaño del elemento para definir ese espacio para tener siempre la misma proporción.

Tenga en cuenta que he agregado el content-box valor para el background:

background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), var(--c) calc(100% - var(--b)) 99%, #0000 ) 50%/calc(100%/var(--f)) 100% no-repeat content-box;

Necesitamos esto porque agregamos relleno y solo queremos que el fondo se establezca en el cuadro de contenido, por lo que debemos decirle explícitamente al fondo que se detenga allí.

Agregar máscara CSS a la mezcla

¡Llegamos a la última parte! Todo lo que tenemos que hacer es ocultar algunas piezas, y listo. Para ello, nos apoyaremos en el mask propiedad y, por supuesto, gradientes.

Aquí hay una figura para ilustrar lo que debemos ocultar o lo que debemos mostrar para ser más precisos.

Mostrando cómo se aplica la máscara a la parte inferior del círculo.

La imagen de la izquierda es lo que tenemos actualmente y la de la derecha es lo que queremos. La parte verde ilustra la máscara que debemos aplicar a la imagen original para obtener el resultado final.

Podemos identificar dos partes de nuestra máscara:

  • Una parte circular en la parte inferior que tiene la misma dimensión y curvatura que el degradado radial que usamos para crear el círculo detrás del avatar.
  • Un rectángulo en la parte superior que cubre el área dentro del contorno. Observe cómo el contorno está fuera del área verde en la parte superior: esa es la parte más importante, ya que permite cortar el contorno para que solo se vea la parte inferior.

Aquí está nuestro CSS final:

img { --s: 280px; /* image size */ --b: 5px; /* border thickness */ --c: #C02942; /* border color */ --f: 1; /* initial scale */ --_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box; --_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b)); width: var(--s); aspect-ratio: 1; padding-top: calc(var(--s)/5); cursor: pointer; border-radius: 0 0 999px 999px; outline: var(--b) solid var(--c); outline-offset: var(--_o); background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), var(--c) calc(100% - var(--b)) 99%, #0000) var(--_g); mask: linear-gradient(#000 0 0) no-repeat 50% calc(-1 * var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%, radial-gradient( circle closest-side, #000 99%, #0000) var(--_g); transform: scale(var(--f)); transition: .5s;
}
img:hover { --f: 1.35; /* hover scale */
}

Vamos a desglosar eso mask propiedad. Para empezar, observe que una similar radial-gradient() del desplegable background la propiedad está ahí. Creé una nueva variable, --_g, para que las partes comunes hagan las cosas menos desordenadas.

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box; mask: radial-gradient( circle closest-side, #000 99%, #0000) var(--_g);

A continuación, hay un linear-gradient() ahí también:

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box; mask: linear-gradient(#000 0 0) no-repeat 50% calc(-1 * var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%, radial-gradient( circle closest-side, #000 99%, #0000) var(--_g);

Esto crea la parte rectangular de la máscara. Su ancho es igual al ancho del degradado radial menos el doble del grosor del borde:

calc(100% / var(--f) - 2 * var(--b))

La altura del rectángulo es igual a la mitad, 50%, del tamaño del elemento.

También necesitamos el gradiente lineal colocado en el centro horizontal (50%) y desplazado desde la parte superior por el mismo valor que el desplazamiento del contorno. Creé otra variable CSS, --_o, para el desplazamiento que definimos previamente:

--_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));

Una de las cosas confusas aquí es que necesitamos un negativas desplazamiento para el contorno (para moverlo de afuera hacia adentro) pero un positivo compensación para el degradado (para moverse de arriba a abajo). Entonces, si se pregunta por qué multiplicamos la compensación, --_o, por -1, ¡bien ahora lo sabes!

Aquí hay una demostración para ilustrar la configuración de gradiente de la máscara:

Pase el cursor sobre lo anterior y vea cómo todo se mueve junto. El cuadro central ilustra la capa de máscara compuesta por dos degradados. Imagínelo como la parte visible de la imagen de la izquierda, ¡y obtendrá el resultado final a la derecha!

Terminando

¡Uf, hemos terminado! Y no solo terminamos con una elegante animación flotante, sino que lo hicimos todo con un solo HTML <img> elemento. ¡Solo eso y menos de 20 líneas de trucos CSS!

Claro, confiamos en algunos pequeños trucos y fórmulas matemáticas para lograr un efecto tan complejo. Pero sabíamos exactamente qué hacer ya que identificamos las piezas que necesitábamos por adelantado.

¿Podríamos haber simplificado el CSS si nos permitiéramos más HTML? Absolutamente. ¡Pero estamos aquí para aprender nuevos trucos de CSS! Este fue un buen ejercicio para explorar los gradientes de CSS, el enmascaramiento, el outline el comportamiento de la propiedad, las transformaciones y mucho más. Si te sentiste perdido en algún momento, entonces definitivamente échale un vistazo. mi serie que utiliza los mismos conceptos generales. A veces ayuda ver más ejemplos y casos de uso para aclarar un punto.

Los dejaré con una última demostración que usa fotos de desarrolladores populares de CSS. ¡No olvides mostrarme una demostración con tu propia imagen para que pueda agregarla a la colección!

Habla con nosotros!

¡Hola! ¿Le puedo ayudar en algo?