Entrances
Zoom In Animation
A clean scale-up entrance with a slight overshoot.
Opens with this animation loaded. Nothing is uploaded — the scene travels in the link.
.zoom-in {
transform: translate3d(180px, 100px, 0);
width: 120px;
height: 120px;
background: #6366f1;
border-radius: 20px;
animation: zoom-in-anim 600ms linear 0ms infinite both;
will-change: transform, opacity;
}
@keyframes zoom-in-anim {
0% {
transform: translate3d(180px, 100px, 0) scale(0.3, 0.3);
opacity: 0;
animation-timing-function: ease-out;
}
60% {
transform: translate3d(180px, 100px, 0) scale(1.07, 1.07);
opacity: 1;
}
100% {
transform: translate3d(180px, 100px, 0);
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.zoom-in {
animation: none;
transition: none;
}
}Why it is built this way
Starting from around 0.3 rather than 0 avoids the moment where the element is invisibly small and appears to pop out of nothing. Pair it with an opacity fade that finishes earlier than the scale.
What it animates
3 properties across 2 CSS declarations. The bar shows when each one is moving.
opacitytransformtransformWhat it costs to run
Opacity, scale x and scale y are handled by the compositor: no layout, no repaint, and the work can run off the main thread. This is the cheap end of CSS animation, which is why it stays smooth on a slow phone where other properties will not.
It runs for 0.60s across 7 keyframe stops, with every property moving over the same window.
3 curves in play — Ease Out, Linear and Back Out on a plain box. Different properties easing differently is how a movement gets texture: the eye reads the mismatch as weight rather than as a mistake.
Accessibility
Starting at opacity 0 makes the reduced-motion guard load-bearing rather than polite: strip it and a reader who asked for less movement gets no content at all, because nothing ever fades it in. It fades over 0.60s alongside scale x and scale y, and the guard skips all of it together. The generated CSS ends on the resting state, so skipping straight there is safe.