Until recently, "scrolling appearance" or "switching between screens" meant event handlers and a bunch of JS code. Today, browser engines take over the render, and we describe the intention in CSS. Pros: fewer dependencies, smoothness at the composer level, more understandable performance limitations.

🚀 Scroll-linked animations: @scroll-timeline
We link the progress of the animation with scrolling. The browser itself gives the "timeline" — it remains to attach keyframes to it.
@scroll-timeline fade-tl {
source: auto; /* ближайший скроллируемый контейнер */
orientation: block; /* по оси прокрутки */
scroll-offsets: 0%, 100%; /* начало/конец */
}
.section__item{
animation: fade-in 1s both linear;
animation-timeline: fade-tl; /* ← магия */
animation-range: entry 0% cover 60%;/* когда и как играться */
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}Where to use: the appearance of cards, parallax, reading progress bar, sticky section headings.
🔁 View Transitions API — "overflows" between states
Native transitions between pages/states without manual synchronization. JS starts the transaction, CSS executes it.
/* CSS */
::view-transition-old(root),
::view-transition-new(root){
animation: fade .35s ease;
}
@keyframes fade {
from { opacity: .001 }
to { opacity: 1 }
}
/* JS (одна строка в обработчике) */
// document.startViewTransition(() => router.navigate('/about'));Feature: you can name elements through view-transition-name and make a "transition" of specific blocks (hero image, title).
📦 Container Queries + animations
Components respond to the width/height of the parent, not the entire viewport. You can smoothly "reassemble" the card when changing the area.
.card{
container-type: inline-size; /* объявили контейнер */
}
@container (min-width: 520px){
.card__media{ inline-size: 280px }
.card__body{ animation: grow .4s ease }
}
@keyframes grow{
from{ transform: scale(.96) }
to { transform: scale(1) }
}🧲 :has() state selector without JS
The parent's reaction to the state/presence of child elements is a trigger for animation.
.accordion:has(input:checked) .accordion__panel{
grid-template-rows: 1fr; /* раскрываем */
transition: grid-template-rows .3s ease;
}
.accordion:not(:has(input:checked)) .accordion__panel{
grid-template-rows: 0fr; /* скрываем */
}
.card:has(:hover){ transform: rotate(1.5deg) scale(1.03); transition:.25s }🧩 Individual transform properties and animation composition
Instead of the "big" transform, independent translate, scale, rotate are available. And the animation-composition property allows you to add effects.
.chip{
translate: 0 8px; /* читаемее и безопаснее */
rotate: 1deg;
transition: translate .25s ease, rotate .25s ease;
}
.chip:hover{ translate: 0 0; rotate: 0deg }
.pulse{ animation: pulse 1.2s infinite ease; }
.float{ animation: float 3s infinite ease alternate; }
.pulse, .float{ animation-composition: add; } /* суммируем эффекты */
@keyframes pulse{ from{scale:1} 50%{scale:1.04} to{scale:1} }
@keyframes float{ from{translate:0 0} to{translate:0 -8px} }📊 Before/After
Task | Earlier (JS) | Now (CSS) |
|---|---|---|
Appearance when scrolling | Scroll handler, classes | @scroll-timeline, animation-range |
Parallax | Wiretapping, miscalculation of positions | Scroll‑timeline on the container |
Navigation between pages | Manual synchronization | View Transitions |
Reaction to the state | Events/class switching | :has() on parent |
Composing effects | Complex timelines in JS | animation-composition: add; |
CSS has grown to a level where most interface animations are created declaratively and run faster. Use @scroll-timeline, View Transitions, :has(), Container Queries, and individual transform properties — and leave JS where you need logic, not a render.
📚 Do you want to delve into the topic?
In the attachment Code you will find detailed lessons on various topics, step-by-step exercises, error analysis and convenient practice right on your phone or browser.
And if you want to be aware of the news, new features and useful materials - subscribe to our Telegram channel. It's cozy, businesslike and with love for code ❤️
💬 What effects have you already moved from JS to CSS and what has given the most return?
