20 CSS Image Hover Effects14 / 20

Pure CSSMIT licensed

CSS Image Split Transition Hover

Avant-garde hover where an image slices horizontally into two moving segments, revealing an action strip in the gap via clip-path.

Published

Live Demo
Try it

The code

<div class="ih-14">
  <div class="ih-14__grid">
    <div class="ih-14__card">
      <div class="ih-14__half ih-14__half--top ih-14__half--1"><span class="ih-14__icon">🎨</span></div>
      <div class="ih-14__half ih-14__half--bot ih-14__half--1"><span class="ih-14__icon">🎨</span></div>
      <span class="ih-14__num">01</span>
      <div class="ih-14__reveal">
        <p class="ih-14__rev-title">Digital Canvas</p>
        <span class="ih-14__rev-cta">View</span>
      </div>
    </div>
    <div class="ih-14__card">
      <div class="ih-14__half ih-14__half--top ih-14__half--2"><span class="ih-14__icon">🌱</span></div>
      <div class="ih-14__half ih-14__half--bot ih-14__half--2"><span class="ih-14__icon">🌱</span></div>
      <span class="ih-14__num">02</span>
      <div class="ih-14__reveal">
        <p class="ih-14__rev-title">Growth Series</p>
        <span class="ih-14__rev-cta">View</span>
      </div>
    </div>
    <div class="ih-14__card">
      <div class="ih-14__half ih-14__half--top ih-14__half--3"><span class="ih-14__icon">⚡</span></div>
      <div class="ih-14__half ih-14__half--bot ih-14__half--3"><span class="ih-14__icon">⚡</span></div>
      <span class="ih-14__num">03</span>
      <div class="ih-14__reveal">
        <p class="ih-14__rev-title">Power Study</p>
        <span class="ih-14__rev-cta">View</span>
      </div>
    </div>
  </div>
</div>
.ih-14,
.ih-14 *,
.ih-14 *::before,
.ih-14 *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.ih-14 ::selection {
  background: #fb7185;
  color: #000;
}

.ih-14 {
  --accent: #fb7185;
  --accent2: #38bdf8;
  --bg: #070710;
  --text: #f1f5f9;
  --muted: #475569;
  --duration: 0.5s;
  --ease: cubic-bezier(0.34,1.56,0.64,1);
  font-family: system-ui,sans-serif;
  background: var(--bg);
  padding: 40px 24px;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ih-14__grid {
  display: grid;
  grid-template-columns: repeat(3,1fr);
  gap: 20px;
  max-width: 780px;
  width: 100%;
}

.ih-14__card {
  position: relative;
  aspect-ratio: 3/4;
  border-radius: 14px;
  overflow: hidden;
  cursor: pointer;
}
/* Each card is split into two halves using clip-path */

.ih-14__half {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--duration) var(--ease), clip-path var(--duration) var(--ease);
  will-change: transform;
}
/* Top half */

.ih-14__half--top {
  clip-path: polygon(0 0,100% 0,100% 50%,0 50%);
}
/* Bottom half */

.ih-14__half--bot {
  clip-path: polygon(0 50%,100% 50%,100% 100%,0 100%);
}
/* On hover: halves slide apart */

.ih-14__card:hover .ih-14__half--top {
  transform: translateY(-6%);
}

.ih-14__card:hover .ih-14__half--bot {
  transform: translateY(6%);
}

.ih-14__half--1 {
  background: linear-gradient(135deg,#1a0533,#4c1d95,#7c3aed);
}

.ih-14__half--2 {
  background: linear-gradient(135deg,#042f2e,#065f46,#10b981);
}

.ih-14__half--3 {
  background: linear-gradient(135deg,#1c1003,#92400e,#f59e0b);
}

.ih-14__icon {
  font-size: 52px;
  opacity: 0.4;
  transition: opacity var(--duration) var(--ease);
}

.ih-14__card:hover .ih-14__icon {
  opacity: 0.1;
}
/* Revealed content in the gap */

.ih-14__reveal {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  padding: 12px 16px;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  opacity: 0;
  clip-path: inset(100% 0 100% 0);
  transition: opacity var(--duration) var(--ease), clip-path var(--duration) var(--ease);
}

.ih-14__card:hover .ih-14__reveal {
  opacity: 1;
  clip-path: inset(0 0 0 0);
}

.ih-14__rev-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
}

.ih-14__rev-cta {
  font-size: 10px;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 20px;
  background: var(--accent);
  color: #fff;
}
/* Static badge */

.ih-14__num {
  position: absolute;
  top: 12px;
  left: 12px;
  font-size: 22px;
  font-weight: 900;
  color: rgba(255,255,255,0.12);
  line-height: 1;
}

@media (prefers-reduced-motion: reduce) {
  .ih-14__half,
    .ih-14__icon,
    .ih-14__reveal {
    transition: none;
  }

  .ih-14__reveal {
    opacity: 1;
    clip-path: inset(0);
  }

  .ih-14__half--top {
    transform: none;
  }

  .ih-14__half--bot {
    transform: none;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Image Hover Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Image Split Transition Hover
Source: https://codefronts.com/motion/css-image-hover-effects/css-image-split-transition-hover/

Avant-garde hover where an image slices horizontally into two moving segments, revealing an action strip in the gap via clip-path.
## HTML
```html
<div class="ih-14">
  <div class="ih-14__grid">
    <div class="ih-14__card">
      <div class="ih-14__half ih-14__half--top ih-14__half--1"><span class="ih-14__icon">🎨</span></div>
      <div class="ih-14__half ih-14__half--bot ih-14__half--1"><span class="ih-14__icon">🎨</span></div>
      <span class="ih-14__num">01</span>
      <div class="ih-14__reveal">
        <p class="ih-14__rev-title">Digital Canvas</p>
        <span class="ih-14__rev-cta">View</span>
      </div>
    </div>
    <div class="ih-14__card">
      <div class="ih-14__half ih-14__half--top ih-14__half--2"><span class="ih-14__icon">🌱</span></div>
      <div class="ih-14__half ih-14__half--bot ih-14__half--2"><span class="ih-14__icon">🌱</span></div>
      <span class="ih-14__num">02</span>
      <div class="ih-14__reveal">
        <p class="ih-14__rev-title">Growth Series</p>
        <span class="ih-14__rev-cta">View</span>
      </div>
    </div>
    <div class="ih-14__card">
      <div class="ih-14__half ih-14__half--top ih-14__half--3"><span class="ih-14__icon">⚡</span></div>
      <div class="ih-14__half ih-14__half--bot ih-14__half--3"><span class="ih-14__icon">⚡</span></div>
      <span class="ih-14__num">03</span>
      <div class="ih-14__reveal">
        <p class="ih-14__rev-title">Power Study</p>
        <span class="ih-14__rev-cta">View</span>
      </div>
    </div>
  </div>
</div>
```
## CSS
```css
.ih-14,
.ih-14 *,
.ih-14 *::before,
.ih-14 *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.ih-14 ::selection {
  background: #fb7185;
  color: #000;
}

.ih-14 {
  --accent: #fb7185;
  --accent2: #38bdf8;
  --bg: #070710;
  --text: #f1f5f9;
  --muted: #475569;
  --duration: 0.5s;
  --ease: cubic-bezier(0.34,1.56,0.64,1);
  font-family: system-ui,sans-serif;
  background: var(--bg);
  padding: 40px 24px;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ih-14__grid {
  display: grid;
  grid-template-columns: repeat(3,1fr);
  gap: 20px;
  max-width: 780px;
  width: 100%;
}

.ih-14__card {
  position: relative;
  aspect-ratio: 3/4;
  border-radius: 14px;
  overflow: hidden;
  cursor: pointer;
}
/* Each card is split into two halves using clip-path */

.ih-14__half {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--duration) var(--ease), clip-path var(--duration) var(--ease);
  will-change: transform;
}
/* Top half */

.ih-14__half--top {
  clip-path: polygon(0 0,100% 0,100% 50%,0 50%);
}
/* Bottom half */

.ih-14__half--bot {
  clip-path: polygon(0 50%,100% 50%,100% 100%,0 100%);
}
/* On hover: halves slide apart */

.ih-14__card:hover .ih-14__half--top {
  transform: translateY(-6%);
}

.ih-14__card:hover .ih-14__half--bot {
  transform: translateY(6%);
}

.ih-14__half--1 {
  background: linear-gradient(135deg,#1a0533,#4c1d95,#7c3aed);
}

.ih-14__half--2 {
  background: linear-gradient(135deg,#042f2e,#065f46,#10b981);
}

.ih-14__half--3 {
  background: linear-gradient(135deg,#1c1003,#92400e,#f59e0b);
}

.ih-14__icon {
  font-size: 52px;
  opacity: 0.4;
  transition: opacity var(--duration) var(--ease);
}

.ih-14__card:hover .ih-14__icon {
  opacity: 0.1;
}
/* Revealed content in the gap */

.ih-14__reveal {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  padding: 12px 16px;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  opacity: 0;
  clip-path: inset(100% 0 100% 0);
  transition: opacity var(--duration) var(--ease), clip-path var(--duration) var(--ease);
}

.ih-14__card:hover .ih-14__reveal {
  opacity: 1;
  clip-path: inset(0 0 0 0);
}

.ih-14__rev-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
}

.ih-14__rev-cta {
  font-size: 10px;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 20px;
  background: var(--accent);
  color: #fff;
}
/* Static badge */

.ih-14__num {
  position: absolute;
  top: 12px;
  left: 12px;
  font-size: 22px;
  font-weight: 900;
  color: rgba(255,255,255,0.12);
  line-height: 1;
}

@media (prefers-reduced-motion: reduce) {
  .ih-14__half,
    .ih-14__icon,
    .ih-14__reveal {
    transition: none;
  }

  .ih-14__reveal {
    opacity: 1;
    clip-path: inset(0);
  }

  .ih-14__half--top {
    transform: none;
  }

  .ih-14__half--bot {
    transform: none;
  }
}
```

How this works

Two div elements are stacked absolutely, each showing the full image background. The top half is clipped with clip-path: polygon(0 0, 100% 0, 100% 50%, 0 50%) and the bottom with the complementary polygon. On hover, a translateY transform moves the top half up and the bottom half down by 6%, opening a gap between them in the centre of the card.

Into this gap, a position: absolute; top: 50% reveal strip animates in using a clip-path: inset(100% 0 100% 0) to inset(0) transition — this clips the strip from both top and bottom edges simultaneously, creating a "wipe open" entrance. The spring easing on the half-split gives an elastic separation feel.

Make it yours

  • Adjust the split percentage from 50% to 40/60 for an asymmetric split that emphasises the bottom content area.
  • Increase the translate amount from 6% to 12% for a more dramatic split, or reduce to 3% for a subtle breathing effect.
  • Add a vertical split variant by switching the clip-path polygons to left/right halves and using translateX instead of translateY.
  • Place contrasting content on each half (different images, different overlays) for a true before/after split reveal.
  • Combine with a rotation: translateY(-6%) rotateX(3deg) on the top half for a 3D page-peel quality.

Gotchas — read before shipping

  • Both half divs must show the SAME background — they are a visual trick where two elements mimic a single image. If their styles diverge, the seam will be visible.
  • The clip-path: inset() animation on the reveal strip requires matching the reveal timing with the split animation or the strip will appear before the gap is wide enough.
  • Chrome can exhibit a 1px gap artefact at the clip boundary between the two halves due to sub-pixel rendering — adding 1px overlap to the clip polygons (e.g. 50.5%) eliminates this.

Browser support

ChromeSafariFirefoxEdge
55+13.1+54+55+

clip-path polygon transitions are well-supported in modern browsers; inset() transitions also work in all modern engines.

Techniques used in this demo

Search CodeFronts

Loading…