---
title: "CSS Image Animations: Zoom, Ken Burns & Carousel Effects"
description: "Six free CSS image animations with previews: hover zoom, Ken Burns pan, grayscale to color, caption slide-up, scroll-snap carousel, and blur-up reveal."
url: https://frontendhelpers.com/css-image-animations
updated: 2026-10-12
---

# CSS image animations

Hover zooms, Ken Burns drift, caption slides, blur-up reveals and a working carousel that needs no JavaScript at all.

## The code for all 6 images & carousels, explained

Every effect above, with the markup to use and the complete CSS. Each one is self-contained: paste the CSS once, add the class, done.

### Hover Zoom

The image scales inside a clipped frame — the frame stays put, so the layout never shifts. The slow ease-out sells the quality.

Markup

```html
<div class="fh-img-zoom">
  <img src="photo.jpg" alt="…" />
</div>
```

CSS

```css
.fh-img-zoom {
  overflow: hidden;
  border-radius: 12px;
}
.fh-img-zoom img,
.fh-img-zoom .fh-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.fh-img-zoom:hover img,
.fh-img-zoom:hover .fh-img { transform: scale(1.12); }
```

### Ken Burns

The slow drift documentary films use on still photos. Keep it subtle and slow (12s+) — it should be felt, not noticed.

Markup

```html
<div class="fh-kenburns">
  <img src="hero.jpg" alt="…" />
</div>
```

CSS

```css
.fh-kenburns {
  overflow: hidden;
  border-radius: 12px;
}
.fh-kenburns img,
.fh-kenburns .fh-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: fh-kenburns 14s ease-in-out infinite alternate;
}
@keyframes fh-kenburns {
  from { transform: scale(1) translate(0, 0); }
  to   { transform: scale(1.18) translate(-2%, 2%); }
}
```

### Grey → Colour

A classic for logo walls and team photos: muted at rest, full colour on attention. filter transitions run on the GPU.

Markup

```html
<div class="fh-desaturate">
  <img src="logo.png" alt="…" />
</div>
```

CSS

```css
.fh-desaturate img,
.fh-desaturate .fh-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1);
  transition: filter 0.4s ease;
}
.fh-desaturate:hover img,
.fh-desaturate:hover .fh-img { filter: grayscale(0); }
```

### Caption Slide-Up

The portfolio staple: the caption hides below the frame and rides up on hover over a gradient scrim that keeps text readable.

Markup

```html
<div class="fh-caption-card">
  <img src="work.jpg" alt="…" />
  <div class="fh-caption">Project title</div>
</div>
```

CSS

```css
.fh-caption-card {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
}
.fh-caption-card img { display: block; width: 100%; height: 100%; object-fit: cover; }
.fh-caption {
  position: absolute;
  inset: auto 0 0 0;
  padding: 14px;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.85));
  color: #fff;
  font-size: 0.8em;
  font-weight: 600;
  transform: translateY(100%);
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.fh-caption-card:hover .fh-caption { transform: translateY(0); }
```

### CSS Carousel

A working carousel with zero JavaScript: scroll-snap docks each slide as the user swipes or scrolls. Pair with anchor links for dot navigation, or a few lines of JS for autoplay.

Markup

```html
<div class="fh-carousel">
  <img src="1.jpg" alt="…" />
  <img src="2.jpg" alt="…" />
  <img src="3.jpg" alt="…" />
</div>
```

CSS

```css
.fh-carousel {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  border-radius: 12px;
}
.fh-carousel > * {
  flex: 0 0 88%;
  min-height: 100%;
  border-radius: 10px;
  scroll-snap-align: center;
  object-fit: cover;
}
```

### Blur-Up Reveal

The Medium-style loading effect: images sharpen into place instead of popping. Add the class when the image's load event fires.

Markup

```html
<img class="fh-blur-load" src="photo.jpg" alt="…" />
```

CSS

```css
.fh-blur-load {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: fh-blurReveal 0.9s ease both;
}
@keyframes fh-blurReveal {
  from { filter: blur(14px); transform: scale(1.06); opacity: 0.6; }
  to   { filter: blur(0);    transform: scale(1);    opacity: 1; }
}
```

## A carousel with no JavaScript

The carousel here uses **CSS scroll snap**: a horizontally scrolling flex container with `scroll-snap-type: x mandatory`, and each slide set to `scroll-snap-align: center`. Swiping or scrolling docks each slide into place with native momentum and no script at all.

This approach is better than most JavaScript carousels for a reason beyond bundle size: it works with the platform. Keyboard scrolling, touch momentum, and screen reader navigation all behave normally, because it's genuinely just a scrolling container. Add anchor links for dot navigation, or a few lines of JavaScript only if you need autoplay.

## Clip the frame, animate the image

Every hover effect here scales the image _inside_ a container with `overflow: hidden`, rather than scaling the container itself. That keeps the frame's footprint fixed, so nothing around it reflows — the difference between a smooth zoom and a page that shifts when you mouse over a thumbnail.

The Ken Burns effect applies the same principle over a long duration: a slow scale and drift that makes a still photograph feel alive in a hero section. Keep it slow — 12 seconds or more — and subtle, or it becomes seasick rather than cinematic.

## Images, layout shift and loading

Animated or not, images are the main cause of Cumulative Layout Shift. Always give them `width` and `height` attributes so the browser reserves space before the file arrives — our [aspect ratio calculator](https://frontendhelpers.com/aspect-ratio-calculator) produces the correct pair. Add `loading="lazy"` to anything below the fold, but never to your hero image, since lazy-loading your largest visible element delays the LCP metric that Google measures.

The blur-up reveal is the loading pattern popularised by Medium: the image sharpens into place instead of popping in. Trigger it by adding the class on the image's `load` event.

## Filters cost more than transforms

The greyscale-to-colour effect uses `filter`, which is GPU-accelerated but heavier than a plain transform — especially on large images. It's fine for a logo wall or a team grid; be more careful applying it to full-bleed hero imagery on lower-powered devices.

## Frequently asked questions

### Can I build a carousel with only CSS?

Yes. A flex container with scroll-snap-type: x mandatory and slides set to scroll-snap-align gives you a working, swipeable carousel with native momentum and keyboard support, with no JavaScript at all.

### Why does my image zoom effect shift the page layout?

You are likely scaling the container rather than the image inside it. Put overflow: hidden on the frame and scale the image within, so the frame keeps its footprint and nothing around it reflows.

### Should I lazy-load all my images?

No. Use loading="lazy" for images below the fold, but never on your hero or largest visible image — lazy-loading it delays the Largest Contentful Paint metric that Google uses to measure your page speed.

## Related categories

- [CSS Marquee: An Infinite Scrolling Ticker Done Right](https://frontendhelpers.com/css-marquee-animation): A seamless CSS marquee: two copies and translateX(-50%), masked edge fades, speed in px/s, a real Pause button for WCAG 2.2.2, and reduced-motion support.
 
- [Hover Effects](https://frontendhelpers.com/css-hover-effects): Lifts, glows, shine sweeps and 3D tilts
 
- [Loaders](https://frontendhelpers.com/css-loading-animations): Skeleton shimmers and progress indicators
 
- [Ambient](https://frontendhelpers.com/css-background-animations): Gradient pans, floating and morphing blobs
