css-native
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCSS Native — Zero-Dependency Animations & Visual Techniques
CSS原生方案——零依赖动画与视觉技术
When to Use CSS Native vs Library
何时选择CSS原生方案 vs 动画库
| Situation | Decision |
|---|---|
| < 3 animations on the page | CSS native |
| Scroll-driven reveal/parallax | CSS native ( |
Enter/exit from | CSS native ( |
| Tooltip/popover positioning | CSS native (anchor positioning) |
| Page transitions (MPA or SPA) | CSS native (View Transitions API) |
| Complex multi-step timeline (5+ tweens) | GSAP |
| Stagger across dynamic list (unknown count) | GSAP or Framer Motion |
| Physics-based spring with interruption | Framer Motion |
| Morph between SVG shapes | GSAP MorphSVG |
Rule of thumb: if you can express it in a + one , stay in CSS. The moment you need imperative control, sequence coordination, or runtime values — reach for a library.
@keyframesanimation-timeline| 场景 | 决策 |
|---|---|
| 页面动画少于3个 | CSS原生方案 |
| 滚动驱动的显示/视差效果 | CSS原生方案( |
从 | CSS原生方案( |
| 提示框/弹出层定位 | CSS原生方案(锚点定位) |
| 页面过渡(MPA或SPA) | CSS原生方案(View Transitions API) |
| 复杂多步骤时间线(5个以上补间动画) | GSAP |
| 动态列表的交错动画(数量未知) | GSAP或Framer Motion |
| 支持中断的物理弹簧动画 | Framer Motion |
| SVG形状间的变形动画 | GSAP MorphSVG |
经验法则:如果可以用 + 单个实现,就用CSS。当你需要命令式控制、序列协调或运行时数值时,再选择动画库。
@keyframesanimation-timelineScroll-Driven Animations
滚动驱动动画
Scroll Progress Timeline
滚动进度时间线
Animate based on scroll position of a container.
css
.progress-bar {
animation: grow-width linear both;
animation-timeline: scroll(root block);
}
@keyframes grow-width {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}- — scroller:
scroll(<scroller> <axis>)|nearest|root, axis:self|block|inline|xy - Default:
scroll(nearest block)
基于容器的滚动位置实现动画。
css
.progress-bar {
animation: grow-width linear both;
animation-timeline: scroll(root block);
}
@keyframes grow-width {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}- — 滚动容器:
scroll(<scroller> <axis>)|nearest|root,轴:self|block|inline|xy - 默认值:
scroll(nearest block)
View Progress Timeline
视图进度时间线
Animate as an element enters/exits the scrollport.
css
.reveal {
animation: fade-in linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(2rem); }
to { opacity: 1; transform: translateY(0); }
}元素进入/退出滚动视口时触发动画。
css
.reveal {
animation: fade-in linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(2rem); }
to { opacity: 1; transform: translateY(0); }
}animation-range
animation-range
Controls which segment of the timeline drives the animation.
css
/* Named ranges: cover | contain | entry | exit | entry-crossing | exit-crossing */
animation-range: entry 0% entry 100%; /* animate during entry only */
animation-range: contain 0% contain 100%; /* animate while fully visible */
animation-range: entry 25% exit 75%; /* custom start/end */Use the Scroll-driven Animations tool to visualize ranges interactively.
控制时间线中驱动动画的片段。
css
/* 命名范围:cover | contain | entry | exit | entry-crossing | exit-crossing */
animation-range: entry 0% entry 100%; /* 仅在进入阶段动画 */
animation-range: contain 0% contain 100%; /* 完全可见时动画 */
animation-range: entry 25% exit 75%; /* 自定义开始/结束点 */使用滚动驱动动画工具交互式可视化范围。
View Transitions API
View Transitions API
Same-Document (SPA)
同文档(SPA)
js
document.startViewTransition(() => {
// Update the DOM synchronously
updateContent();
});css
/* Control the transition animation */
::view-transition-old(root) {
animation: fade-out 200ms ease-out;
}
::view-transition-new(root) {
animation: fade-in 300ms ease-in;
}
/* Named transitions for specific elements */
.hero-image { view-transition-name: hero; }
::view-transition-group(hero) {
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}js
document.startViewTransition(() => {
// 同步更新DOM
updateContent();
});css
/* 控制过渡动画 */
::view-transition-old(root) {
animation: fade-out 200ms ease-out;
}
::view-transition-new(root) {
animation: fade-in 300ms ease-in;
}
/* 特定元素的命名过渡 */
.hero-image { view-transition-name: hero; }
::view-transition-group(hero) {
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}Cross-Document (MPA)
跨文档(MPA)
css
/* Both pages need this */
@view-transition { navigation: auto; }
/* Outgoing page */
.card { view-transition-name: card-detail; }
/* Incoming page */
.detail-hero { view-transition-name: card-detail; }css
/* 两个页面都需要添加 */
@view-transition { navigation: auto; }
/* 离开页面 */
.card { view-transition-name: card-detail; }
/* 进入页面 */
.detail-hero { view-transition-name: card-detail; }view-transition-class (group styling)
view-transition-class(组样式)
css
.card { view-transition-class: card; }
::view-transition-group(*.card) {
animation-duration: 350ms;
animation-timing-function: var(--ease-spring);
}css
.card { view-transition-class: card; }
::view-transition-group(*.card) {
animation-duration: 350ms;
animation-timing-function: var(--ease-spring);
}@starting-style
@starting-style
Native enter animations from — no JS timing hacks.
display: nonecss
.dialog {
opacity: 1;
transform: translateY(0);
transition: opacity 300ms ease, transform 300ms ease,
display 300ms allow-discrete;
@starting-style {
opacity: 0;
transform: translateY(-1rem);
}
}
.dialog[hidden] {
opacity: 0;
transform: translateY(-1rem);
display: none;
}Key rules:
- (or shorthand
transition-behavior: allow-discretein the transition) enables transitioningallow-discreteanddisplayoverlay - block defines the "from" state when the element first renders
@starting-style - Combine with and
[popover]for native modals with zero JS animation code<dialog>
原生支持从状态进入的动画——无需JS计时技巧。
display: nonecss
.dialog {
opacity: 1;
transform: translateY(0);
transition: opacity 300ms ease, transform 300ms ease,
display 300ms allow-discrete;
@starting-style {
opacity: 0;
transform: translateY(-1rem);
}
}
.dialog[hidden] {
opacity: 0;
transform: translateY(-1rem);
display: none;
}关键规则:
- (或在transition中使用简写
transition-behavior: allow-discrete)启用allow-discrete和display属性的过渡overlay - 块定义元素首次渲染时的“起始”状态
@starting-style - 结合和
[popover]实现零JS动画代码的原生模态框<dialog>
Anchor Positioning
锚点定位
CSS-native positioning of tooltips, popovers, and floating UI relative to a trigger.
css
.trigger {
anchor-name: --my-trigger;
}
.tooltip {
position: fixed;
position-anchor: --my-trigger;
position-area: top center;
margin-bottom: 0.5rem;
/* Fallback if no space on top */
position-try-fallbacks: --bottom;
}
@position-try --bottom {
position-area: bottom center;
margin-top: 0.5rem;
}Combine with for animated tooltips:
@starting-stylecss
.tooltip[popover]:popover-open {
opacity: 1;
transform: scale(1);
transition: opacity 150ms ease, transform 150ms ease,
display 150ms allow-discrete, overlay 150ms allow-discrete;
@starting-style {
opacity: 0;
transform: scale(0.96);
}
}CSS原生实现提示框、弹出层和浮动UI相对于触发元素的定位。
css
.trigger {
anchor-name: --my-trigger;
}
.tooltip {
position: fixed;
position-anchor: --my-trigger;
position-area: top center;
margin-bottom: 0.5rem;
/* 顶部无空间时的回退方案 */
position-try-fallbacks: --bottom;
}
@position-try --bottom {
position-area: bottom center;
margin-top: 0.5rem;
}结合实现带动画的提示框:
@starting-stylecss
.tooltip[popover]:popover-open {
opacity: 1;
transform: scale(1);
transition: opacity 150ms ease, transform 150ms ease,
display 150ms allow-discrete, overlay 150ms allow-discrete;
@starting-style {
opacity: 0;
transform: scale(0.96);
}
}Container Queries + Contextual Animations
容器查询 + 上下文动画
Adapt animations to the component's container size, not the viewport.
css
.card-container {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 400px) {
.card-content {
animation: slide-in-right 400ms var(--ease-out-expo);
}
}
@container card (max-width: 399px) {
.card-content {
animation: fade-in 300ms ease;
}
}Container-relative units in keyframes:
css
@keyframes slide-in-right {
from { transform: translateX(10cqw); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}根据组件的容器大小而非视口适配动画。
css
.card-container {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 400px) {
.card-content {
animation: slide-in-right 400ms var(--ease-out-expo);
}
}
@container card (max-width: 399px) {
.card-content {
animation: fade-in 300ms ease;
}
}关键帧中的容器相对单位:
css
@keyframes slide-in-right {
from { transform: translateX(10cqw); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}Advanced Visual Techniques
高级视觉技术
clip-path Transitions
clip-path过渡
css
.reveal {
clip-path: inset(0 100% 0 0);
transition: clip-path 600ms cubic-bezier(0.77, 0, 0.175, 1);
}
.reveal.visible {
clip-path: inset(0 0 0 0);
}Shape morphing: transition between , , , — as long as the function type and point count match.
circle()ellipse()polygon()inset()css
.reveal {
clip-path: inset(0 100% 0 0);
transition: clip-path 600ms cubic-bezier(0.77, 0, 0.175, 1);
}
.reveal.visible {
clip-path: inset(0 0 0 0);
}形状变形:在、、、之间过渡——只要函数类型和点数匹配即可。
circle()ellipse()polygon()inset()backdrop-filter
backdrop-filter
css
.glass {
background: oklch(0.98 0.01 250 / 0.6);
backdrop-filter: blur(12px) saturate(1.8);
-webkit-backdrop-filter: blur(12px) saturate(1.8); /* Safari */
}css
.glass {
background: oklch(0.98 0.01 250 / 0.6);
backdrop-filter: blur(12px) saturate(1.8);
-webkit-backdrop-filter: blur(12px) saturate(1.8); /* Safari */
}mix-blend-mode
mix-blend-mode
css
.overlay-text {
mix-blend-mode: difference;
color: white; /* inverts over any background */
}css
.overlay-text {
mix-blend-mode: difference;
color: white; /* 在任何背景上都会反转颜色 */
}Mesh Gradients (approximated)
网格渐变(近似实现)
css
.mesh {
background:
radial-gradient(at 20% 30%, oklch(0.7 0.2 310) 0%, transparent 50%),
radial-gradient(at 80% 60%, oklch(0.6 0.18 250) 0%, transparent 50%),
radial-gradient(at 50% 80%, oklch(0.75 0.15 170) 0%, transparent 50%),
oklch(0.15 0.02 280);
}css
.mesh {
background:
radial-gradient(at 20% 30%, oklch(0.7 0.2 310) 0%, transparent 50%),
radial-gradient(at 80% 60%, oklch(0.6 0.18 250) 0%, transparent 50%),
radial-gradient(at 50% 80%, oklch(0.75 0.15 170) 0%, transparent 50%),
oklch(0.15 0.02 280);
}conic-gradient Effects
conic-gradient效果
css
.spinner {
background: conic-gradient(from 0deg, transparent 0%, oklch(0.7 0.15 250) 100%);
border-radius: 50%;
mask: radial-gradient(farthest-side, transparent calc(100% - 4px), black calc(100% - 4px));
animation: spin 1s linear infinite;
}css
.spinner {
background: conic-gradient(from 0deg, transparent 0%, oklch(0.7 0.15 250) 100%);
border-radius: 50%;
mask: radial-gradient(farthest-side, transparent calc(100% - 4px), black calc(100% - 4px));
animation: spin 1s linear infinite;
}Do Not
避坑指南
| BAD | GOOD | Why |
|---|---|---|
| | |
Animate | Animate | Layout-triggering properties force reflow on every frame — composite-only properties run on GPU |
| Scroll-driven animations without fallback | | Firefox only added support in v128+, older Safari versions lack support |
| Always pair with | Without it, |
Anchor positioning without | Always define fallback positions | Element clips out of viewport if primary position has no space |
| Use | |
| 错误做法 | 正确做法 | 原因 |
|---|---|---|
| | |
动画 | 动画 | 触发布局的属性会在每一帧强制重排——仅合成属性在GPU上运行 |
| 滚动驱动动画不做兼容处理 | | Firefox在v128+才添加支持,旧版Safari不支持 |
使用 | 始终搭配 | 没有它, |
锚点定位不设置 | 始终定义回退位置 | 如果主位置没有空间,元素会超出视口 |
滚动驱动动画使用 | 滚动驱动动画使用 | |
Deep dive
深入学习
| Need | Reference |
|---|---|
Browser-support tables, fallback patterns and progressive-enhancement strategies for scroll-driven animations, View Transitions, | |
| 需求 | 参考资料 |
|---|---|
滚动驱动动画、View Transitions、 | |