How to build 5 clip-path hover effects

How to build 5 clip-path hover effects

ยท

8 min read

Featured on Hashnode

Today let's build 5 awesome hover effects with CSS clip-path!

Read the full article or watch me code this on Youtube:

Result

Basic Setup

Let's first setup a few basic things like:

  1. A custom font via google fonts
  2. A fiew css Variables for easy color customization in :root
  3. Basic font configuration on the html tag
  4. A CSS grid that will put all the links we're going to style in a nicely aligned fashion.
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;500;600;700;800;900&display=swap');

:root {
  --bg-color: #2b2b2b;
  --fg-color: rgba(255, 255, 255, 0.9);
  --bd-color: rgba(255, 255, 255, 0.2);

    --default-bg: 
    linear-gradient(322deg, #ba4aff, rgba(186, 74, 255, 0) 70%),
    linear-gradient(178deg, #008aff, rgba(0, 138, 255, 0) 70%), 
    linear-gradient(24deg, #00ffc6, rgba(0, 255, 198, 0) 35%),
    linear-gradient(0deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1));
}

html {
  font-size: 34px;
  font-weight: 200;
}

body {
  min-height: 100vh;

  display: grid;
  place-content: center;
  grid-template-columns: repeat(3, max-content);
  gap: 1rem;

  background: var(--bg-color);

  font-family: "Montserrat", sans-serif;
}

Each link is going to have the same basic style, that means: No text decoration, a little border & padding as well as a small flex-box setup that makes the text centered horizontally and vertically.

The position: relative; setting on the link tag itself is important as it makes the position: absolute; on the ::before pseudo-element work.

a {
  position: relative;

  padding: 0.25rem 1rem;

  text-decoration: none;
  color: var(--fg-color);

  border: 1px solid var(--bd-color);

  display: flex;
  justify-content: center;
  align-items: center;
}

The ::before pseudo-element is going to be spanned across the entire link (top, left, bottom & right all set to 0) and is put behind the link's text through setting -1 as its z-index. To make the clip-path effect well visible, the pseudo-element gets a few gradients as background.

a::before {
  content: '';

  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;

  background: var(--default-bg);

  z-index: -1;

  transition: clip-path 400ms ease-out;
}

In order to have all clip path configurations smoothly transition from one state to another, the clip-path property is made transitionable by putting it into the transition CSS property.

Style: Next

<a href="#"
   class="next">next</a>

Now for the first style the clip path gets an arrowy shape on the left and right side. So in the initial state both edges are positioned completely to the left.

a.next::before {
  clip-path: polygon(
    0% 0%,
    -0.5rem 0%,
    0% 50%,
    -0.5rem 100%,
    0% 100%,
    -0.5rem 50%
  );
}

On hover the right edge of the arrow moves from left to right and upon mouse down the left edge of the arrow also moves from the left to right.

a.next:hover::before {
  clip-path: polygon(
    -0.5rem 0%,
    100% 0%,
    calc(100% + 0.5rem) 50%,
    100% 100%,
    -0.5rem 100%,
    0% 50%
  );
}

Note the number of points in each clip path. In order make transition: clip-path work, the number of points in each polygon must be the same. If the number of points deviates from one state to another the browser will simply jump from one state to another without any smooth transition.

a.next:active::before {
  clip-path: polygon(
    100% 0%,
    100% 0%,
    calc(100% + 0.5rem) 50%,
    100% 100%,
    100% 100%,
    calc(100% + 0.5rem) 50%
  );
}

Style: Dot

<a href="#"
   class="dot">dot</a>

In this simple style, the ::before pseudo element is clipped down to a circle which is at the center of the link. Upon hover it smoothly expands and on mouse down spans the entire link.

The circle function takes the radius as the first argument before the at keyword and after that the position of the circle's center as x and y coordinate.

a.dot::before {
   clip-path: circle(
     0%
     at
     50% 50%
   );
}
a.dot:hover::before {
  clip-path: circle(
    50%
    at
    50% 50%
  );
}
a.dot:active::before {
  clip-path: circle(
    100%
    at
    50% 50%
  );
}

Style: Sunrise

<a href="#"
   class="sunrise">sunrise</a>

Let the sun rise ๐Ÿ˜Ž The initial state is a large circle at the center of the link but shifted down outside the viewport.

a.sunrise::before {
  clip-path: circle(
    40%
    at
    50% 200%
  );
}

In order to produce the effect of a rising sun it is shifted upwards upon hover.

a.sunrise:hover::before {
  clip-path: circle(
    50%
    at
    50% 100%
  );
}

On mouse down the circle is moved to the center of the link and expanded completely.

a.sunrise:active::before {
  clip-path: circle(
    100%
    at
    50% 50%
  );
}

Style: Slant

<a href="#"
   class="slant">slant</a>

This time again both the left and the right edge are positioned to the very left. The lower point of each edge is shifted to the left by 1rem.

a.slant::before {
  clip-path: polygon(
    0% 0%,
    0% 0%,
    -1rem 100%,
    -1rem 100%
  );
}

So upon hover, first the right edge is moved to the right, ...

a.slant:hover::before {
  clip-path: polygon(
    0% 0%,
    calc(100% + 1rem) 0%,
    100% 100%,
    -1rem 100%
  );
}

... and on mouse down the left edge will also move to the right.

a.slant:active::before {
  clip-path: polygon(
    calc(100% + 1rem) 0%,
    calc(100% + 1rem) 0%,
    100% 100%,
    100% 100%
  );
}

Style: Liquid

<a href="#"
   class="liquid">liquid</a>

For the last style let's create a slight impression of a liquid rising/flowing. For that a larger ellipse is created. The ellipse function is similar to the circle except that it takes the horizontal and the vertical radius as the first two arguments before the at keyword.

a.liquid::before {
  clip-path: ellipse(
    100% 50%
    at
    50% 200%
  );
}

On hover the ellipse is moved upwards and changes its vertical radius to produce a soft curve.

a.liquid:hover::before {
  clip-path: ellipse(
    100% 250%
    at
    50% 200%
  );
}

And on mouse down the ellipse is vertically scaled down to zero as well as moved upwards to the upper edge.

a.liquid:active::before {
  clip-path: ellipse(
    100% 0%
    at
    50% 0%
  );
}