HTML & CSS: The Document ModelVisual DesignLesson 16 of 18

The Magic of Clip Path

clip-path is often used for trimming elements into specific shapes, like triangles and circles. But what if I told you that it's also great for animations?

In this lesson, we'll dive into clip-path and explore some of the cool things you can do with it. Once you read it, you'll start seeing this CSS property being used everywhere.

The Basics

The clip-path property clips an element into a specific shape. We create a clipping region, and content outside this region becomes invisible while content inside stays visible. This lets us turn a rectangle into a circle, for example.

.avatar {
  clip-path: circle(50%);
}

Here's the key insight: clip-path has no effect on layout. An element with clip-path occupies the same space as it would without it, just like transform. The element is still there—we're just hiding parts of it.

Positioning with Coordinates

We position shapes using a coordinate system that starts at the top left corner (0, 0).

.element {
  clip-path: circle(50% at 50% 50%);
}

This means: create a circle with radius 50%, positioned at 50% from the left and 50% from the top (the center).

You can position the circle anywhere:

.element {
  /* Circle in top-left corner */
  clip-path: circle(30% at 0% 0%);

  /* Circle in bottom-right corner */
  clip-path: circle(30% at 100% 100%);
}

Shape Functions

CSS provides several shape functions for clip-path:

circle() — Creates circular clips

.element {
  clip-path: circle(50%); /* Fills the element */
  clip-path: circle(100px at center); /* Fixed size */
}

ellipse() — Creates oval clips

.element {
  clip-path: ellipse(50% 30% at center);
}

polygon() — Creates any shape with points

/* Triangle */
.element {
  clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}

/* Hexagon */
.element {
  clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}

inset() — Creates rectangular clips with offsets

This is the most powerful shape for animations. The values define how much to cut from each side: top, right, bottom, left.

.element {
  /* No clipping - show everything */
  clip-path: inset(0);

  /* Hide everything */
  clip-path: inset(100%);

  /* Hide right half */
  clip-path: inset(0 50% 0 0);

  /* Hide left half */
  clip-path: inset(0 0 0 50%);

  /* Hide top half */
  clip-path: inset(50% 0 0 0);

  /* 10px border on all sides */
  clip-path: inset(10px);

  /* Rounded corners */
  clip-path: inset(0 round 16px);
}

The inset() function is what we'll use for animations. It lets us precisely control which parts of an element are visible.

Why Inset is Perfect for Animation

Think about what inset(0 50% 0 0) means: cut 0 from top, 50% from right, 0 from bottom, 0 from left. This hides the right half.

Now, if we animate from inset(0 100% 0 0) to inset(0 0 0 0), we reveal the element from left to right:

.reveal {
  clip-path: inset(0 100% 0 0); /* Start: fully hidden from right */
  transition: clip-path 0.5s ease;
}

.reveal.active {
  clip-path: inset(0 0 0 0); /* End: fully visible */
}

This is the foundation of clip-path animations: we animate the clipping region to reveal or hide content.

Building a Comparison Slider

Let's apply this knowledge. Comparison sliders show "before" and "after" by overlaying two images.

The technique:

  1. Stack two images on top of each other using position: absolute
  2. Apply clip-path: inset(0 50% 0 0) to the top image to hide its right half
  3. Adjust the inset value based on drag position
.comparison {
  position: relative;
}

.comparison .after {
  position: absolute;
  inset: 0;
  clip-path: inset(0 50% 0 0); /* Hide right half */
  transition: clip-path 0.1s ease;
}

When the user drags, we calculate the percentage and update the clip-path:

// If user drags to 30% from left
afterImage.style.clipPath = 'inset(0 70% 0 0)';

This approach is hardware-accelerated and doesn't require extra DOM elements. The clipped image stays in place—we're just revealing more or less of it.

The Overlay Technique

Here's where it gets interesting. Instead of clipping a single element, we can overlay two identical elements and clip one to create highlight effects.

Imagine a row of tabs. The inactive state is gray text on a transparent background. The active state is white text on a blue background. Instead of changing styles on click, we:

  1. Render the tabs twice, stacked on top of each other
  2. The bottom layer shows the inactive state (gray text)
  3. The top layer shows the active state (white text on blue background)
  4. We clip the top layer to only show the active tab
<div class="tabs-wrapper">
  <!-- Bottom layer: inactive appearance -->
  <div class="tabs">
    <button>Payments</button>
    <button>Balances</button>
    <button>Customers</button>
  </div>

  <!-- Top layer: active appearance, clipped -->
  <div class="tabs tabs-active" style="clip-path: inset(0 75% 0 0 round 17px)">
    <button>Payments</button>
    <button>Balances</button>
    <button>Customers</button>
  </div>
</div>

The top layer is positioned absolutely over the bottom layer. The clip-path only reveals the portion covering the active tab. When the user clicks a different tab, we animate the clip-path to slide over to the new tab.

This creates a smooth, fluid highlight effect that feels like a physical indicator sliding between tabs.

Calculating the Clip Path

To clip the overlay to exactly cover one tab, we need to calculate the tab's position:

function updateClipPath(activeTab) {
  const container = document.querySelector('.tabs-active');
  const containerWidth = container.offsetWidth;

  // Get the active tab's position
  const tabLeft = activeTab.offsetLeft;
  const tabWidth = activeTab.offsetWidth;
  const tabRight = tabLeft + tabWidth;

  // Convert to percentages
  const clipLeft = (tabLeft / containerWidth) * 100;
  const clipRight = 100 - (tabRight / containerWidth) * 100;

  // Apply the clip-path
  container.style.clipPath = `inset(0 ${clipRight}% 0 ${clipLeft}% round 17px)`;
}

The math:

  • clipLeft: How much to cut from the left (as a percentage)
  • clipRight: How much to cut from the right (as a percentage)
  • round 17px: Adds rounded corners to the clipped region

When combined with a CSS transition, the clip-path smoothly animates from one tab to another:

.tabs-active {
  transition: clip-path 0.25s ease;
}

Why This Technique Works

The overlay technique has several advantages:

  1. GPU-accelerated: clip-path animations run on the compositor thread
  2. No layout shifts: Elements don't move or resize
  3. Smooth color transitions: The active state "slides" into view rather than snapping
  4. Works with any content: Icons, text, badges—everything gets the treatment

The key insight is that we're not changing the tabs themselves. We're revealing and hiding a pre-styled overlay. This separation makes the animation buttery smooth.

Performance Considerations

clip-path is generally performant, but keep these tips in mind:

  • Use simple shapes: inset() and circle() are faster than complex polygon() shapes
  • Avoid clipping large areas: Clipping a small element is cheaper than clipping a full-page section
  • Test on mobile: Some older devices struggle with clip-path animations

Other Visual Effects

While clip-path is powerful for animations, CSS offers other visual effects worth knowing:

Transforms

Move, rotate, scale, and skew elements without affecting layout:

.element {
  transform: translateX(100px) rotate(45deg) scale(1.2);
  transform-origin: center; /* Pivot point */
}

Transforms are GPU-accelerated and ideal for hover effects and animations.

Filters

Apply visual effects like blur and brightness:

.element {
  filter: blur(5px);
  filter: brightness(1.2) contrast(1.1);
  filter: grayscale(100%);
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1));
}

Backdrop Filter

Apply filters to content behind an element (frosted glass effect):

.glass-panel {
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
}

Masks

Hide parts of elements using images or gradients:

.fade-out {
  mask-image: linear-gradient(to bottom, black 70%, transparent);
}

Masks work with any content, unlike gradient overlays which require knowing the background color.

Check Your Understanding

What does clip-path: inset(0 50% 0 0) do?

Not quite. The correct answer is highlighted.

Why is the overlay technique effective for tab animations?

Not quite. The correct answer is highlighted.

What's the advantage of clip-path animations over width/height animations?

Not quite. The correct answer is highlighted.
To add rounded corners to an inset clip-path, use: clip-path: inset(0 16px);
Not quite.Expected: round

Practice

Build a tab component that uses the overlay technique. The icons and basic structure are provided—your job is to implement the clip-path animation that slides between tabs.

Summary

  • clip-path basics: Clips elements into shapes without affecting layout
  • inset() function: Defines rectangular clips with top, right, bottom, left offsets
  • Animation foundation: Animate from inset(0 100% 0 0) to inset(0) to reveal content
  • Overlay technique: Stack two identical elements, clip the styled one to create sliding highlights
  • Calculating position: Use offsetLeft and offsetWidth to compute clip percentages
  • Performance: clip-path is GPU-accelerated; prefer inset() over complex polygons
  • Transitions: Add transition: clip-path 0.25s ease for smooth animations
  • Round corners: Use inset(... round Xpx) for rounded clip regions