Have you learnt that sort of impact the place somebody’s head is poking via a circle or gap? The well-known Porky Pig animation the place he waves goodbye whereas coming out of a collection of purple rings is the right instance, and Kilian Valkhof really re-created that right here on CSS-Methods some time again.
I’ve an identical thought however tackled a unique method and with a sprinkle of animation. I believe it’s fairly sensible and makes for a neat hover impact you should use on one thing like your individual avatar.
See that? We’re going to make a scaling animation the place the avatar appears to pop proper out of the circle it’s in. Cool, proper? Don’t have a look at the code and let’s construct this animation collectively step-by-step.
The HTML: Only one factor
In the event you haven’t checked the code of the demo and you’re questioning what number of div
s this’ll take, then cease proper there, as a result of our markup is nothing however a single picture factor:
<img src="" alt="">
Sure, a single factor! The difficult a part of this train is utilizing the smallest quantity of code potential. If in case you have been following me for some time, you have to be used to this. I attempt onerous to search out CSS options that may be achieved with the smallest, most maintainable code potential.
I wrote a collection of articles right here on CSS-Methods the place I discover totally different hover results utilizing the identical HTML markup containing a single factor. I’m going into element on gradients, masking, clipping, outlines, and even format methods. I extremely suggest checking these out as a result of I’ll re-use lots of the methods on this submit.
A picture file that’s sq. with a clear background will work finest for what we’re doing. Right here’s the one I’m utilizing if you would like begin with that.
I’m hoping to see numerous examples of this as potential utilizing actual photos — so please share your remaining outcome within the feedback if you’re finished so we are able to construct a group!
Earlier than leaping into CSS, let’s first dissect the impact. The picture will get greater on hover, so we’ll for certain use remodel: scale()
in there. There’s a circle behind the avatar, and a radial gradient ought to do the trick. Lastly, we’d like a method to create a border on the backside of the circle that creates the looks of the avatar behind the circle.
Let’s get to work!
The dimensions impact
Let’s begin by including the remodel:
img {
width: 280px;
aspect-ratio: 1;
cursor: pointer;
transition: .5s;
}
img:hover {
remodel: scale(1.35);
}
Nothing sophisticated but, proper? Let’s transfer on.
The circle
We stated that the background could be a radial gradient. That’s good as a result of we are able to create onerous stops between the colours of a radial gradient, which make it seem like we’re drawing a circle with strong strains.
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 {
remodel: scale(1.35);
}
Observe the CSS variable, --b
, I’m utilizing there. It represents the thickness of the “border” which is admittedly simply getting used to outline the onerous coloration stops for the purple a part of the radial gradient.
The subsequent step is to play with the gradient measurement on hover. The circle must maintain its measurement because the picture grows. Since we’re making use of a scale()
transformation, we really have to lower the dimensions of the circle as a result of it in any other case scales up with the avatar. So, whereas the picture scales up, we’d like the gradient to scale down.
Let’s begin by defining a CSS variable, --f
, that defines the “scale issue”, and use it to set the dimensions of the circle. I’m utilizing 1
because the default worth, as in that’s the preliminary scale for the picture and the circle that we remodel from.
Here’s a demo as an example the trick. Hover to see what is going on behind the scenes:
I added a 3rd coloration to the radial-gradient
to raised determine the world of the gradient on hover:
radial-gradient(
circle closest-side,
#ECD078 calc(99% - var(--b)),
#C02942 calc(100% - var(--b)) 99%,
lightblue
);
Now now we have to place our background on the heart of the circle and ensure it takes up the total top. I prefer to declare all the pieces instantly on the background
shorthand property, so we are able to add our background positioning and ensure it doesn’t repeat by tacking on these values proper after the radial-gradient()
:
background: radial-gradient() 50% / calc(100% / var(--f)) 100% no-repeat;
The background is positioned on the heart (50%
), has a width equal to calc(100%/var(--f))
, and has a top equal to 100%
.
Nothing scales when --f
is the same as 1
— once more, our preliminary scale. In the meantime, the gradient takes up the total width of the container. Once we improve --f
, the factor’s measurement grows — due to the scale()
remodel — and the gradient’s measurement decreases.
Right here’s what we get once we apply all of this to our demo:
We’re getting nearer! We’ve the overflow impact on the high, however we nonetheless want to cover the underside a part of the picture, so it appears like it’s coming out of the circle relatively than sitting in entrance of it. That’s the tough a part of this complete factor and is what we’re going to do subsequent.
The underside border
I first tried tackling this with the border-bottom
property, however I used to be unable to discover a method to match the dimensions of the border to the dimensions to the circle. Right here’s one of the best I might get and you’ll instantly see it’s mistaken:
The precise resolution is to make use of the define
property. Sure, define
, not border
. In a earlier article, I present how define
is highly effective and permits us to create cool hover results. Mixed with outline-offset
, now we have precisely what we’d like for our impact.
The concept is to set an define
on the picture and regulate its offset to create the underside border. The offset will depend upon the scaling issue the identical method the gradient measurement did.
Now now we have our backside “border” (really an define
) mixed with the “border” created by the gradient to create a full circle. We nonetheless want to cover parts of the define
(from the highest and the edges), which we’ll get to in a second.
Right here’s our code to date, together with a pair extra CSS variables you should use to configure the picture measurement (--s
) and the “border” coloration (--c
):
img {
--s: 280px; /* picture measurement */
--b: 5px; /* border thickness */
--c: #C02942; /* border coloration */
--f: 1; /* preliminary scale */
width: var(--s);
aspect-ratio: 1;
cursor: pointer;
border-radius: 0 0 999px 999px;
define: var(--b) strong 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;
remodel: scale(var(--f));
transition: .5s;
}
img:hover {
--f: 1.35; /* hover scale */
}
Since we’d like a round backside border, we added a border-radius
on the underside aspect, permitting the define
to match the curvature of the gradient.
The calculation used on outline-offset
is much more easy than it appears. By default, define
is drawn outdoors of the factor’s field. And in our case, we’d like it to overlap the factor. Extra exactly, we’d like it to comply with the circle created by the gradient.
Once we scale the factor, we see the house between the circle and the sting. Let’s not overlook that the concept is to maintain the circle on the similar measurement after the size transformation runs, which leaves us with the house we’ll use to outline the define’s offset as illustrated within the above determine.
Let’s not overlook that the second factor is scaled, so our outcome can also be scaled… which implies we have to divide the outcome by f
to get the true offset worth:
Offset = ((f - 1) * S/2) / f = (1 - 1/f) * S/2
We add a unfavourable signal since we’d like the define to go from the surface to the within:
Offset = (1/f - 1) * S/2
Right here’s a fast demo that reveals how the define follows the gradient:
Chances are you’ll already see it, however we nonetheless want the underside define to overlap the circle relatively than letting it bleed via it. We are able to do this by eradicating the border’s measurement from the offset:
outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2) - var(--b));
Now we have to discover how one can take away the highest half from the define. In different phrases, we solely need the underside a part of the picture’s define
.
First, let’s add house on the high with padding to assist keep away from the overlap on the high:
img {
--s: 280px; /* picture measurement */
--b: 5px; /* border thickness */
--c: #C02942; /* border coloration */
--f: 1; /* preliminary scale */
width: var(--s);
aspect-ratio: 1;
padding-block-start: calc(var(--s)/5);
/* and so on. */
}
img:hover {
--f: 1.35; /* hover scale */
}
There is no such thing as a explicit logic to that high padding. The concept is to make sure the define doesn’t contact the avatar’s head. I used the factor’s measurement to outline that house to all the time have the identical proportion.
Observe that I’ve added the content-box
worth to the 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;
We’d like this as a result of we added padding and we solely need the background set to the content material field, so we should explicitly inform the background to cease there.
Including CSS masks to the combination
We reached the final half! All we have to do is to cover some items, and we’re finished. For this, we’ll depend on the masks
property and, in fact, gradients.
Here’s a determine as an example what we have to cover or what we have to present to be extra correct
The left picture is what we at the moment have, and the suitable is what we would like. The inexperienced half illustrates the masks we should apply to the unique picture to get the ultimate outcome.
We are able to determine two elements of our masks:
- A round half on the backside that has the identical dimension and curvature because the radial gradient we used to create the circle behind the avatar
- A rectangle on the high that covers the world contained in the define. Discover how the define is outdoors the inexperienced space on the high — that’s crucial half, because it permits the define to be reduce in order that solely the underside half is seen.
Right here’s our remaining CSS:
img {
--s: 280px; /* picture measurement */
--b: 5px; /* border thickness */
--c: #C02942; /* border coloration */
--f: 1; /* preliminary 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;
define: var(--b) strong 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);
masks:
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);
remodel: scale(var(--f));
transition: .5s;
}
img:hover {
--f: 1.35; /* hover scale */
}
Let’s break down that masks
property. For starters, discover {that a} related radial-gradient()
from the background
property is in there. I created a brand new variable, --_g
, for the frequent elements to make issues much less cluttered.
--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;
masks:
radial-gradient(
circle closest-side,
#000 99%,
#0000) var(--_g);
Subsequent, there’s a linear-gradient()
in there as properly:
--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;
masks:
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);
This creates the rectangle a part of the masks. Its width is the same as the radial gradient’s width minus twice the border thickness:
calc(100% / var(--f) - 2 * var(--b))
The rectangle’s top is the same as half, 50%
, of the factor’s measurement.
We additionally want the linear gradient positioned on the horizontal heart (50%
) and offset from the highest by the identical worth because the define’s offset. I created one other CSS variable, --_o
, for the offset we beforehand outlined:
--_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));
One of many complicated issues right here is that we’d like a unfavourable offset for the define (to maneuver it from outdoors to inside) however a constructive offset for the gradient (to maneuver from high to backside). So, when you’re questioning why we multiply the offset, --_o
, by -1
, properly, now !
Here’s a demo as an example the masks’s gradient configuration:
Hover the above and see how all the pieces transfer collectively. The center field illustrates the masks layer composed of two gradients. Think about it because the seen a part of the left picture, and also you get the ultimate outcome on the suitable!
Wrapping up
Oof, we’re finished! And never solely did we wind up with a slick hover animation, however we did all of it with a single HTML <img>
factor. Simply that and fewer than 20 strains of CSS trickery!
Certain, we relied on some little methods and math formulation to succeed in such a fancy impact. However we knew precisely what to do since we recognized the items we would have liked up-front.
May now we have simplified the CSS if we allowed ourselves extra HTML? Completely. However we’re right here to be taught new CSS methods! This was an excellent train to discover CSS gradients, masking, the define
property’s habits, transformations, and a complete bunch extra. In the event you felt misplaced at any level, then positively take a look at my collection that makes use of the identical common ideas. It typically helps to see extra examples and use circumstances to drive a degree residence.
I’ll go away you with one final demo that makes use of images of well-liked CSS builders. Don’t overlook to indicate me a demo with your individual picture so I can add it to the gathering!