Xlera8

Un effet de survol fantaisiste pour votre avatar

Connaissez-vous ce genre d'effet où la tête de quelqu'un passe à travers un cercle ou un trou ? La célèbre animation Porky Pig où il dit au revoir tout en sortant d'une série d'anneaux rouges en est l'exemple parfait, et Kilian Valkhof a en fait recréé cela ici sur CSS-Tricks il y a quelque temps.

J'ai une idée similaire mais abordée d'une manière différente et avec une pincée d'animation. Je pense que c'est assez pratique et donne un effet de survol soigné que vous pouvez utiliser sur quelque chose comme votre propre avatar.

Regarde ça? Nous allons créer une animation de mise à l'échelle où l'avatar semble sortir du cercle dans lequel il se trouve. Cool, non ? Ne regardez pas le code et construisons cette animation ensemble étape par étape.

Le HTML : un seul élément

Si vous n'avez pas vérifié le code de la démo et que vous vous demandez combien divs cela prendra, puis arrêtez-vous là, car notre balisage n'est rien d'autre qu'un seul élément d'image :

<img src="" alt="">

Oui, un seul élément ! La partie difficile de cet exercice consiste à utiliser la plus petite quantité de code possible. Si vous avez été me suivre pendant un certain temps, vous devriez être habitué à cela. Je m'efforce de trouver des solutions CSS qui peuvent être réalisées avec le code le plus petit et le plus maintenable possible.

J'ai écrit une série d'articles ici sur CSS-Tricks où j'explore différents effets de survol en utilisant le même balisage HTML contenant un seul élément. J'entre dans les détails sur les dégradés, le masquage, le détourage, les contours et même les techniques de mise en page. Je recommande fortement de les vérifier car je réutiliserai de nombreuses astuces dans cet article.

Un fichier image carré avec un arrière-plan transparent fonctionnera mieux pour ce que nous faisons. Voici celui que j'utilise si vous voulez commencer par ça.

Conçu par cang

J'espère voir beaucoup d'exemples de cela possible en utilisant de vraies images - alors s'il vous plaît partagez votre résultat final dans les commentaires lorsque vous avez terminé afin que nous puissions créer une collection !

Avant de sauter dans CSS, disséquons d'abord l'effet. L'image s'agrandit au survol, nous utiliserons donc à coup sûr transform: scale() là-dedans. Il y a un cercle derrière l'avatar et un dégradé radial devrait faire l'affaire. Enfin, nous avons besoin d'un moyen de créer une bordure au bas du cercle qui crée l'apparence de l'avatar derrière le cercle.

Mettons-nous au travail!

L'effet d'échelle

Commençons par ajouter la transformation :

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

Rien de bien compliqué pour le moment, n'est-ce pas ? Allons-nous en.

Le cercle

Nous avons dit que le fond serait un dégradé radial. C'est parfait car nous pouvons créer des arrêts durs entre les couleurs d'un dégradé radial, ce qui donne l'impression que nous dessinons un cercle avec des lignes pleines.

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);
}

Notez la variable CSS, --b, j'utilise là. Il représente l'épaisseur de la "bordure" qui est en fait juste utilisée pour définir les arrêts de couleur durs pour la partie rouge du dégradé radial.

L'étape suivante consiste à jouer avec la taille du dégradé au survol. Le cercle doit conserver sa taille au fur et à mesure que l'image grandit. Puisque nous appliquons une scale() transformation, nous devons en fait diminuer la taille du cercle car sinon, il s'agrandit avec l'avatar. Ainsi, pendant que l'image s'agrandit, nous avons besoin que le dégradé se réduise.

Commençons par définir une variable CSS, --f, qui définit le "facteur d'échelle", et utilisez-le pour définir la taille du cercle. j'utilise 1 comme valeur par défaut, car c'est l'échelle initiale de l'image et du cercle à partir duquel nous transformons.

Voici une démo pour illustrer l'astuce. Survolez pour voir ce qui se passe dans les coulisses :

J'ai ajouté une troisième couleur au radial-gradient pour mieux identifier la zone du dégradé au survol :

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

Nous devons maintenant positionner notre arrière-plan au centre du cercle et nous assurer qu'il occupe toute la hauteur. J'aime tout déclarer directement sur le background propriété raccourcie, afin que nous puissions ajouter notre positionnement d'arrière-plan et nous assurer qu'il ne se répète pas en appliquant ces valeurs juste après le radial-gradient():

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

Le fond est placé au centre (50%), a une largeur égale à calc(100%/var(--f)), et a une hauteur égale à 100%.

Rien n'évolue quand --f est égal à 1 — encore une fois, notre échelle initiale. Pendant ce temps, le dégradé occupe toute la largeur du conteneur. Quand on augmente --f, la taille de l'élément augmente — grâce à la scale() transform — et la taille du dégradé diminue.

Voici ce que nous obtenons lorsque nous appliquons tout cela à notre démo :

On se rapproche ! Nous avons l'effet de débordement en haut, mais nous devons toujours masquer la partie inférieure de l'image, de sorte qu'elle semble sortir du cercle plutôt que de s'asseoir devant. C'est la partie délicate de tout cela et c'est ce que nous allons faire ensuite.

La bordure inférieure

J'ai d'abord essayé de résoudre ce problème avec le border-bottom propriété, mais je n'ai pas trouvé de moyen de faire correspondre la taille de la bordure à la taille du cercle. Voici le meilleur que j'ai pu obtenir et vous pouvez immédiatement voir que c'est faux :

La vraie solution est d'utiliser le outline propriété. Oui, outline, Pas border. En un article précédent, je montre comment outline est puissant et nous permet de créer des effets de survol sympas. Combiné avec outline-offset, nous avons exactement ce dont nous avons besoin pour notre effet.

L'idée est de fixer un outline sur l'image et ajustez son décalage pour créer la bordure inférieure. Le décalage dépendra du facteur d'échelle de la même manière que la taille du dégradé.

Nous avons maintenant notre "bordure" inférieure (en fait une outline) combiné avec la "bordure" créée par le dégradé pour créer un cercle complet. Nous devons encore cacher des parties du outline (du haut et des côtés), auquel nous reviendrons dans un instant.

Voici notre code jusqu'à présent, y compris quelques variables CSS supplémentaires que vous pouvez utiliser pour configurer la taille de l'image (--s) et la couleur de la "bordure" (--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 */
}

Puisque nous avons besoin d'une bordure inférieure circulaire, nous avons ajouté un border-radius sur le côté inférieur, permettant le outline pour correspondre à la courbure du dégradé.

Le calcul utilisé sur outline-offset est beaucoup plus simple qu'il n'y paraît. Par défaut, outline est tiré au contrôle de la boîte de l'élément. Et dans notre cas, nous en avons besoin pour chevauchement l'élément. Plus précisément, nous en avons besoin pour suivre le cercle créé par le dégradé.

Schéma de la transition de fond.

Lorsque nous redimensionnons l'élément, nous voyons l'espace entre le cercle et le bord. N'oublions pas que l'idée est de garder le cercle à la même taille après l'exécution de la transformation d'échelle, ce qui nous laisse l'espace que nous utiliserons pour définir le décalage du contour comme illustré dans la figure ci-dessus.

N'oublions pas que le deuxième élément est mis à l'échelle, donc notre résultat est également mis à l'échelle… ce qui signifie que nous devons diviser le résultat par f pour obtenir la valeur réelle du décalage :

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

Nous ajoutons un signe négatif car nous avons besoin que le contour aille de l'extérieur vers l'intérieur :

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

Voici une démo rapide qui montre comment le contour suit le dégradé :

Vous le voyez peut-être déjà, mais nous avons toujours besoin que le contour inférieur chevauche le cercle plutôt que de le laisser saigner à travers. Nous pouvons le faire en supprimant la taille de la bordure du décalage :

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

Nous devons maintenant trouver comment supprimer la partie supérieure du contour. En d'autres termes, nous ne voulons que la partie inférieure de l'image outline.

Tout d'abord, ajoutons de l'espace en haut avec un rembourrage pour éviter le chevauchement en haut :

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 */
}

Il n'y a pas de logique particulière à ce rembourrage supérieur. L'idée est de s'assurer que le contour ne touche pas la tête de l'avatar. J'ai utilisé la taille de l'élément pour définir cet espace afin d'avoir toujours la même proportion.

Notez que j'ai ajouté le content-box valeur à la 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;

Nous en avons besoin car nous avons ajouté un rembourrage et nous voulons uniquement que l'arrière-plan soit défini sur la zone de contenu, nous devons donc dire explicitement à l'arrière-plan de s'arrêter là.

Ajouter un masque CSS au mélange

Nous sommes arrivés à la dernière partie ! Tout ce que nous devons faire est de cacher quelques pièces, et nous avons terminé. Pour cela, nous nous appuierons sur mask propriété et, bien sûr, les gradients.

Voici une figure pour illustrer ce qu'il faut cacher ou ce qu'il faut montrer pour être plus précis

Montrant comment le masque s'applique à la partie inférieure du cercle.

L'image de gauche est ce que nous avons actuellement, et la droite est ce que nous voulons. La partie verte illustre le masque que nous devons appliquer à l'image originale pour obtenir le résultat final.

Nous pouvons identifier deux parties de notre masque :

  • Une partie circulaire en bas qui a la même dimension et la même courbure que le dégradé radial que nous avons utilisé pour créer le cercle derrière l'avatar
  • Un rectangle en haut qui couvre la zone à l'intérieur du contour. Remarquez comment le contour est en dehors de la zone verte en haut - c'est la partie la plus importante, car il permet de couper le contour de sorte que seule la partie inférieure soit visible.

Voici notre 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 */
}

Décomposons cela mask propriété. Pour commencer, notez qu'un radial-gradient() du background la propriété est là-dedans. J'ai créé une nouvelle variable, --_g, pour les parties communes afin de rendre les choses moins encombrantes.

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

Ensuite, il y a un linear-gradient() dedans aussi :

--_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);

Cela crée la partie rectangle du masque. Sa largeur est égale à la largeur du dégradé radial moins deux fois l'épaisseur de la bordure :

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

La hauteur du rectangle est égale à la moitié, 50%, de la taille de l'élément.

Nous avons également besoin du dégradé linéaire placé au centre horizontal (50%) et décalé du haut de la même valeur que le décalage du contour. J'ai créé une autre variable CSS, --_o, pour le décalage défini précédemment :

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

L'une des choses déroutantes ici est que nous avons besoin d'un négatif décalage pour le contour (pour le déplacer de l'extérieur vers l'intérieur) mais un positif décalage pour le dégradé (pour se déplacer de haut en bas). Donc, si vous vous demandez pourquoi nous multiplions le décalage, --_o, par -1, eh bien, maintenant vous savez!

Voici une démo pour illustrer la configuration du dégradé du masque :

Survolez ce qui précède et voyez comment tout bouge ensemble. La boîte du milieu illustre le calque de masque composé de deux dégradés. Imaginez-le comme la partie visible de l'image de gauche, et vous obtenez le résultat final à droite !

Emballage en place

Ouf, nous avons terminé ! Et non seulement nous nous sommes retrouvés avec une animation de survol fluide, mais nous avons tout fait avec un seul HTML <img> élément. Rien que ça et moins de 20 lignes de supercherie CSS !

Bien sûr, nous nous sommes appuyés sur quelques petites astuces et formules mathématiques pour obtenir un effet aussi complexe. Mais nous savions exactement quoi faire puisque nous avions identifié les pièces dont nous avions besoin dès le départ.

Aurions-nous pu simplifier le CSS si nous nous étions permis plus de HTML ? Absolument. Mais nous sommes ici pour apprendre de nouvelles astuces CSS ! C'était un bon exercice pour explorer les dégradés CSS, le masquage, le outline le comportement de la propriété, les transformations et bien plus encore. Si vous vous êtes senti perdu à un moment donné, alors vérifiez définitivement ma série qui utilise les mêmes concepts généraux. Il est parfois utile de voir plus d'exemples et de cas d'utilisation pour enfoncer le clou.

Je vous laisse avec une dernière démo qui utilise des photos de développeurs CSS populaires. N'oubliez pas de me montrer une démo avec votre propre image pour que je puisse l'ajouter à la collection !

Discutez avec nous

Salut! Comment puis-je t'aider?