Demo 06 · Lab
A still with slow motion
One image and six lines of CSS. It travels a few percent across 48 seconds, out and back, and you never catch it moving.
Use this when
You already have an image strong enough to carry a hero, or the page has to hold its frame rate on hardware you can't predict.
Skip it if: You don't have that image. There is no effect layered over the picture to carry it, so whatever the picture is, that is what the page is.
The prompt
The whole technique, which is short enough to read rather than prompt. Point your coding agent at it if you want it wired into a component, or just paste the CSS.
.hero-still {
position: absolute;
inset: 0;
background: url("/your-still.jpg") center / cover no-repeat;
animation: drift 48s ease-in-out infinite alternate;
will-change: transform;
}
@keyframes drift {
from { transform: scale(1.06) translate3d(-1.6%, -1.1%, 0); }
to { transform: scale(1.15) translate3d( 1.6%, 1.1%, 0); }
}
/* Not optional. For some visitors motion is an accessibility need. */
@media (prefers-reduced-motion: reduce) {
.hero-still {
animation: none;
transform: scale(1.06);
}
}
THE FOUR THINGS THAT MATTER
1. 48 seconds. This is the entire trick. At 10 seconds it reads as a slideshow; at 48 it reads as depth, and a visitor who scrolls past in four seconds registers only that the page felt alive. If you change one number, do not change this one first.
(The demo on this page runs at 18 seconds, not 48, and deliberately. At the real production speed it travels about a pixel and a half per second, which is correct on a page someone reads for minutes and useless in a box someone looks at for five seconds. Build yours at 48.)
2. Scale above 1 at both ends. At scale 1.06 there is 3% of overflow on each side and the translate travels 1.6%, so the edge never comes into frame. Start from 1.0 and you will get a hairline of background sliding in at the corners.
3. `alternate`, not a loop back to the start. It eases out and back, so there is no seam. A plain `infinite` loop snaps home every 48 seconds and the snap is the one thing a viewer will notice.
4. `will-change: transform` so the browser promotes the layer once, rather than repainting the image on every frame.
WORTH ADDING
A vignette over the top if your still was composed at a different aspect ratio to the box you are dropping it into, a radial gradient from transparent at 40% to about 55% black at the edges keeps the subject in the frame rather than running off it. And a light grain layer at around 5% opacity if the image has large smooth gradients in it, for the same reason every other effect on this page has grain: eight-bit displays cannot render a smooth ramp between two close colours without banding.
WHAT NOT TO DO
Do not add a parallax-on-scroll to this as well. The whole argument for the technique is that it costs nothing and never fights the content; a scroll handler gives back both of those. If you want the background to respond to the reader, you have chosen the wrong technique from this article.
Performance notes
- Technique
- CSS transform on an image
- JavaScript
- None
- Effect CSS
- 0.4 KB
- Image
- 80 KB JPEG, 1080²
- Dependencies
- None
- Motion cycle
- 18 s here · 48 s in production
- Runtime cost
- None. No loop, no canvas
- Reduced motion
- Static composition