20 CSS Image Hover Effects 14 / 20
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.
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> <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}} .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.
Customize
- 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%to12%for a more dramatic split, or reduce to3%for a subtle breathing effect. - Add a vertical split variant by switching the clip-path polygons to left/right halves and using
translateXinstead oftranslateY. - 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.
Watch out for
- 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
1pxoverlap to the clip polygons (e.g.50.5%) eliminates this.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 55+ | 13.1+ | 54+ | 55+ |
clip-path polygon transitions are well-supported in modern browsers; inset() transitions also work in all modern engines.