Are you aware that sort of impact the place somebody’s head is poking by way of a circle or gap? The well-known Porky Pig animation the place he waves goodbye whereas coming out of a sequence of crimson rings is the right instance, and Kilian Valkhof really re-created that right here on CSS-Tips some time again.
I’ve an analogous concept 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 utilize 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 take a look at the code and let’s construct this animation collectively step-by-step.
The HTML: Only one aspect
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 aspect:
<img src="" alt="">
Sure, a single aspect! The difficult a part of this train is utilizing the smallest quantity of code attainable. When you’ve got been following me for some time, try to be used to this. I strive onerous to seek out CSS options that may be achieved with the smallest, most maintainable code attainable.
I wrote a sequence of articles right here on CSS-Tips the place I discover completely different hover results utilizing the identical HTML markup containing a single aspect. I am going into element on gradients, masking, clipping, outlines, and even structure strategies. I extremely suggest checking these out as a result of I’ll re-use most of the tips on this put up.
A picture file that’s sq. with a clear background will work greatest for what we’re doing. Right here’s the one I’m utilizing if you need begin with that.
I’m hoping to see a lot of examples of this as attainable utilizing actual photos — so please share your ultimate outcome within the feedback whenever you’re carried out so we will 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 positive use rework: scale()
in there. There’s a circle behind the avatar, and a radial gradient ought to do the trick. Lastly, we want a approach 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 rework:
img {
width: 280px;
aspect-ratio: 1;
cursor: pointer;
transition: .5s;
}
img:hover {
rework: scale(1.35);
}
Nothing sophisticated but, proper? Let’s transfer on.
The circle
We mentioned that the background can be a radial gradient. That’s excellent as a result of we will create onerous stops between the colours of a radial gradient, which make it seem like we’re drawing a circle with stable 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 {
rework: scale(1.35);
}
Observe the CSS variable, --b
, I’m utilizing there. It represents the thickness of the “border” which is de facto simply getting used to outline the onerous coloration stops for the crimson a part of the radial gradient.
The following step is to play with the gradient dimension on hover. The circle must hold its dimension because the picture grows. Since we’re making use of a scale()
transformation, we really have to lower the scale of the circle as a result of it in any other case scales up with the avatar. So, whereas the picture scales up, we want 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 scale 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 rework from.
Here’s a demo as an instance the trick. Hover to see what is going on behind the scenes:
I added a 3rd coloration to the radial-gradient
to raised establish the realm of the gradient on hover:
radial-gradient(
circle closest-side,
#ECD078 calc(99% - var(--b)),
#C02942 calc(100% - var(--b)) 99%,
lightblue
);
Now we’ve 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 immediately on the background
shorthand property, so we will 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. After we enhance --f
, the aspect’s dimension grows — because of the scale()
rework — and the gradient’s dimension decreases.
Right here’s what we get after we apply all of this to our demo:
We’re getting nearer! Now we have 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 slightly than sitting in entrance of it. That’s the tough a part of this entire 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 approach to match the scale of the border to the scale to the circle. Right here’s the very best I may get and you may instantly see it’s unsuitable:
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
, we’ve precisely what we want 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 rely upon the scaling issue the identical method the gradient dimension did.
Now we’ve 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 up to now, together with a pair extra CSS variables you should utilize to configure the picture dimension (--s
) and the “border” coloration (--c
):
img {
--s: 280px; /* picture dimension */
--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) stable 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;
rework: scale(var(--f));
transition: .5s;
}
img:hover {
--f: 1.35; /* hover scale */
}
Since we want 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 exterior of the aspect’s field. And in our case, we want it to overlap the aspect. Extra exactly, we want it to observe the circle created by the gradient.
After we scale the aspect, we see the area between the circle and the sting. Let’s not overlook that the thought is to maintain the circle on the identical dimension after the size transformation runs, which leaves us with the area we’ll use to outline the define’s offset as illustrated within the above determine.
Let’s not overlook that the second aspect is scaled, so our outcome can be scaled… which suggests 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 damaging signal since we want 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:
You could already see it, however we nonetheless want the underside define to overlap the circle slightly than letting it bleed by way of it. We are able to do this by eradicating the border’s dimension 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 area on the high with padding to assist keep away from the overlap on the high:
img {
--s: 280px; /* picture dimension */
--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 isn’t a specific logic to that high padding. The concept is to make sure the define doesn’t contact the avatar’s head. I used the aspect’s dimension to outline that area 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 carried out. For this, we’ll depend on the masks
property and, in fact, gradients.
Here’s a determine as an instance 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 correct 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 establish two components 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 realm contained in the define. Discover how the define is exterior the inexperienced space on the high — that’s a very powerful half, because it permits the define to be reduce in order that solely the underside half is seen.
Right here’s our ultimate CSS:
img {
--s: 280px; /* picture dimension */
--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) stable 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);
rework: 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 widespread components 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 effectively:
--_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 aspect’s dimension.
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 want a damaging offset for the define (to maneuver it from exterior to inside) however a optimistic offset for the gradient (to maneuver from high to backside). So, when you’re questioning why we multiply the offset, --_o
, by -1
, effectively, now you realize!
Here’s a demo as an instance 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 correct!
Wrapping up
Oof, we’re carried out! And never solely did we wind up with a slick hover animation, however we did all of it with a single HTML <img>
aspect. Simply that and fewer than 20 strains of CSS trickery!
Positive, we relied on some little tips and math formulation to succeed in such a fancy impact. However we knew precisely what to do since we recognized the items we wanted up-front.
Might we’ve simplified the CSS if we allowed ourselves extra HTML? Completely. However we’re right here to study new CSS tips! This was a superb 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 try my sequence that makes use of the identical common ideas. It generally helps to see extra examples and use instances to drive some extent house.
I’ll go away you with one final demo that makes use of pictures of standard CSS builders. Don’t overlook to point out me a demo with your individual picture so I can add it to the gathering!