Friday, April 19, 2024
HomeProgrammingA Fancy Hover Impact For Your Avatar | CSS-Tips

A Fancy Hover Impact For Your Avatar | CSS-Tips


Have you learnt that type of impact the place somebody’s head is poking by 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 proper instance, and Kilian Valkhof truly re-created that right here on CSS-Tips some time again.

I’ve the same concept however tackled a unique means 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 have a look at the code and let’s construct this animation collectively step-by-step.

The HTML: Only one component

For those who haven’t checked the code of the demo and you’re questioning what number of divs this’ll take, then cease proper there, as a result of our markup is nothing however a single picture component:

<img src="" alt="">

Sure, a single component! The difficult a part of this train is utilizing the smallest quantity of code attainable. When you have been following me for some time, you need 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 totally different hover results utilizing the identical HTML markup containing a single component. I’m going into element on gradients, masking, clipping, outlines, and even structure strategies. I extremely advocate checking these out as a result of I’ll re-use most of the tips on this publish.

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 in order for you begin with that.

Designed by Cang

I’m hoping to see a lot of examples of this as attainable utilizing actual photos — so please share your ultimate end result within the feedback if you’re carried out so we will construct a set!

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

Notice the CSS variable, --b, I’m utilizing there. It represents the thickness of the “border” which is basically simply getting used to outline the onerous colour stops for the crimson a part of the radial gradient.

The subsequent step is to play with the gradient measurement on hover. The circle must preserve its measurement because the picture grows. Since we’re making use of a scale() transformation, we truly must 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 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 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 colour to the radial-gradient to raised determine 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 middle of the circle and ensure it takes up the complete top. I wish to declare all the pieces instantly 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 middle (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 complete width of the container. Once we improve --f, the component’s measurement grows — because of the scale() remodel — and the gradient’s measurement decreases.

Right here’s what we get after we apply all of this to our demo:

We’re getting nearer! We’ve the overflow impact on the prime, however we nonetheless want to cover the underside a part of the picture, so it seems to be 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 dimensions of the border to the dimensions to the circle. Right here’s the very best I may get and you’ll 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 thought is to set an define on the picture and alter its offset to create the underside border. The offset will rely on the scaling issue the identical means the gradient measurement did.

Now we’ve our backside “border” (truly 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 perimeters), which we’ll get to in a second.

Right here’s our code to this point, together with a pair extra CSS variables you should utilize to configure the picture measurement (--s) and the “border” colour (--c):

img {
  --s: 280px; /* picture measurement */
  --b: 5px; /* border thickness */
  --c: #C02942; /* border colour */
  --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 want a round backside border, we added a border-radius on the underside facet, permitting the define to match the curvature of the gradient.

The calculation used on outline-offset is much more easy than it seems to be. By default, define is drawn outdoors of the component’s field. And in our case, we want it to overlap the component. Extra exactly, we want it to comply with the circle created by the gradient.

Diagram of the background transition.

Once we scale the component, we see the house between the circle and the sting. Let’s not neglect that the concept is to maintain the circle on the identical measurement after the size transformation runs, which leaves us with the house we are going to use to outline the define’s offset as illustrated within the above determine.

Let’s not neglect that the second component is scaled, so our end result can also be scaled… which implies we have to divide the end result by f to get the actual 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 skin to the within:

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

Right here’s a fast demo that exhibits how the define follows the gradient:

Chances are you’ll already see it, however we nonetheless want the underside define to overlap the circle slightly than letting it bleed by 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 you 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 prime with padding to assist keep away from the overlap on the prime:

img {
  --s: 280px; /* picture measurement */
  --b: 5px;   /* border thickness */
  --c: #C02942; /* border colour */
  --f: 1; /* preliminary scale */

  width: var(--s);
  aspect-ratio: 1;
  padding-block-start: calc(var(--s)/5);
  /* and many others. */
}
img:hover {
  --f: 1.35; /* hover scale */
}

There isn’t any explicit logic to that prime padding. The thought is to make sure the define doesn’t contact the avatar’s head. I used the component’s measurement to outline that house to at all times have the identical proportion.

Notice 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 combo

We reached the final half! All we have to do is to cover some items, and we’re carried out. For this, we are going to 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

Showing how the mask applies to the bottom portion of the circle.

The left picture is what we at present have, and the appropriate is what we would like. The inexperienced half illustrates the masks we should apply to the unique picture to get the ultimate end result.

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 prime that covers the realm contained in the define. Discover how the define is outdoors the inexperienced space on the prime — that’s crucial half, because it permits the define to be minimize in order that solely the underside half is seen.

Right here’s our ultimate CSS:

img {
  --s: 280px; /* picture measurement */
  --b: 5px; /* border thickness */
  --c: #C02942; /* border colour */
  --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 widespread 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 component’s measurement.

We additionally want the linear gradient positioned on the horizontal middle (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 outdoors to inside) however a constructive offset for the gradient (to maneuver from prime to backside). So, for those who’re questioning why we multiply the offset, --_o, by -1, properly, now you recognize!

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 end result on the appropriate!

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> component. Simply that and fewer than 20 strains of CSS trickery!

Certain, 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 train to discover CSS gradients, masking, the define property’s habits, transformations, and an entire bunch extra. For those who felt misplaced at any level, then positively take a look at my sequence that makes use of the identical normal ideas. It typically helps to see extra examples and use circumstances to drive a degree dwelling.

I’ll go away you with one final demo that makes use of images of standard CSS builders. Don’t neglect to point out me a demo with your individual picture so I can add it to the gathering!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments