动画与交互 —— 过渡、关键帧、滚动驱动、视图过渡
一个让用户“哇”出来的动效2015 年我在一个旅游网站工作。产品经理把一个设计稿扔到我面前首页的 banner 上一行标题从左侧滑入背景图片缓慢缩放下面的卡片随着页面滚动一个一个淡入。他淡淡地说“这种效果在别的网站上看起来很酷我们也做吧。”我心想不难。用 CSStransition做悬停效果用keyframes做入场动画用scroll事件监听滚动位置给卡片加class…… 吭哧吭哧写了三天JS 里堆满了addEventListener、getBoundingClientRect、requestAnimationFrame还要处理节流、销毁事件。上线后用户在低端手机上滑动时明显掉帧产品经理指着 Chrome 的 “jank” 警告问我为什么不能“丝滑一点”。那时候我不知道有一种叫做scroll-driven animations的 CSS 特性即将出现它可以把滚动动画完全交给浏览器合成器线程不再占用主线程。更不知道几年后view-transitions会让页面切换像原生 App 一样平滑。如今是 2026 年CSS 动画已经从一个“点缀”升级为交互的核心手段。这篇文章我会带你完整梳理 CSS 动画的四大支柱过渡、关键帧动画、滚动驱动动画、视图过渡。既有基础概念也有实战案例和未来展望。读完你会明白如何用最少的代码实现让用户“哇”出来的流畅体验。第一章过渡transition—— 动画的基石1.1 从“突变”到“渐变”transition可能是 CSS 里最简单的动画工具。它让属性值的变化不是瞬间完成而是在一段时间内平滑过渡。.button{background-color:blue;transition:background-color 0.3s ease;}.button:hover{background-color:red;}当你鼠标悬停时背景色从蓝色渐变为红色耗时 0.3 秒。如果没有transition它会“啪”地一下变红毫无过渡。1.2 可过渡的属性不是所有属性都能过渡。只有那些可插值的属性才可以即数值、颜色、长度、百分比等。例如颜色color,background-color,border-color尺寸width,height,padding,margin,font-size变换transform透明度opacity阴影box-shadow,text-shadow不能过渡的属性包括display可以用visibility过渡、position但top/left可以只要它们有数值变化、z-index等。1.3 transition 的完整语法.el{transition-property:all;/* 要过渡的属性all 表示所有 */transition-duration:0.3s;/* 持续时间 */transition-timing-function:ease;/* 缓动函数 */transition-delay:0s;/* 延迟时间 */}/* 简写 */.el{transition:all 0.3s ease 0s;}缓动函数决定动画的速度曲线ease慢-快-慢默认linear匀速ease-in加速ease-out减速ease-in-out先加速后减速cubic-bezier()自定义贝塞尔曲线1.4 实战技巧使用transform代替top/left做位移transform在合成器线程中执行不会触发布局重排reflow性能远优于top/left。/* 不推荐 */.box:hover{left:100px;}/* 推荐 */.box:hover{transform:translateX(100px);}1.5 多个属性分别过渡.btn{transition:transform 0.2s ease,background-color 0.3s linear,box-shadow 0.15s;}1.6 过渡结束事件JavaScript 中可以监听transitionend事件在动画完成后执行逻辑。element.addEventListener(transitionend,(){console.log(过渡结束);});过渡简单、兼容性好是现代 Web 动效的基石。但它只能实现“状态变化”的动画无法做循环或复杂序列动画。这时就需要关键帧动画。第二章关键帧动画keyframes—— 表达复杂时序2.1 定义关键帧keyframes让你定义动画的中间状态关键帧浏览器自动插值中间帧。keyframesslideIn{0%{transform:translateX(-100%);opacity:0;}100%{transform:translateX(0);opacity:1;}}.box{animation:slideIn 0.5s ease-out;}也可以使用from和to替代0%和100%。2.2 animation 属性详解.box{animation-name:slideIn;animation-duration:1s;animation-timing-function:ease;animation-delay:0s;animation-iteration-count:infinite;/* 循环次数 */animation-direction:alternate;/* 方向normal, reverse, alternate, alternate-reverse */animation-fill-mode:forwards;/* 动画结束后保持最后一帧状态 */animation-play-state:running;/* 播放或暂停 */}/* 简写 */.box{animation:slideIn 1s ease 0s infinite alternate forwards;}2.3 多个关键帧与百分比你可以定义任意多的关键帧实现复杂运动。keyframesbounce{0%, 100%{transform:translateY(0);}50%{transform:translateY(-30px);}80%{transform:translateY(-10px);}}2.4 无限循环与方向.loader{animation:spin 1s linear infinite;}keyframesspin{to{transform:rotate(360deg);}}2.5 动画与过渡的配合通常用过渡做悬停、焦点等交互反馈用关键帧做入场、循环、吸引注意的动效。第三章滚动驱动动画Scroll-driven Animations—— 新时代的杀手锏这是 2023 年以来 CSS 最激动人心的新特性之一。过去滚动视差、滚动进度条等效果必须用 JS 监听滚动事件性能差、代码臃肿。现在可以用 CSS 直接声明“当滚动到某个位置时动画完成百分之多少”。3.1 核心概念滚动驱动动画分为两种滚动进度动画Scroll Progress动画进度与滚动容器的滚动位置绑定。视图进度动画View Progress动画进度与元素在视口中的可见度绑定。3.2 滚动进度动画假设你有一个进度条随着页面滚动而填充keyframesgrow{from{width:0%;}to{width:100%;}}.progress{animation:grow linear;animation-timeline:scroll();/* 关键绑定滚动进度 */position:fixed;top:0;left:0;height:4px;background:blue;}animation-timeline: scroll()表示动画的进度由最近的滚动容器的滚动位置决定。scroll()可以指定滚动轴scroll(block)默认垂直滚动或scroll(inline)水平滚动。你也可以指定滚动容器scroll(scroller nearest)。3.3 视图进度动画当某个元素进入视口时动画开始完全离开时动画结束。keyframesfadeIn{from{opacity:0;transform:translateY(20px);}to{opacity:1;transform:translateY(0);}}.card{animation:fadeIn linear;animation-timeline:view();/* 基于元素自身的视图进度 */animation-range:entry 0% entry 100%;/* 可选定义动画范围 */}animation-range进一步控制动画的起止点。例如entry 0% entry 100%元素从开始进入视口到完全进入动画从 0% 到 100%。exit 0% exit 100%元素从开始离开到完全离开。contain 0% contain 100%元素完全在视口内时触发。3.4 实战滚动视差.parallax{animation:parallax linear;animation-timeline:scroll();animation-range:exit 0% exit 200%;transform-origin:center;}keyframesparallax{from{transform:translateY(0);}to{transform:translateY(200px);}}当元素向上滚动离开视口时它会以较慢的速度移动产生视差效果。3.5 浏览器支持截至 2026 年滚动驱动动画已在 Chrome 115、Edge 115、Firefox 110需启用标志、Safari 16.4 中得到部分支持。完整支持仍在推进但生产环境可通过supports渐进增强。supports(animation-timeline:scroll()){.progress{animation-timeline:scroll();}}3.6 与 GSAP 等库的对比GSAP 的 ScrollTrigger 插件功能极其强大但滚动驱动动画的优势是原生、高性能运行在合成器线程适合简单到中度的滚动联动效果。复杂的时间线、交错动画仍然需要 JS 库。第四章视图过渡View Transitions—— SPA 级别的平滑切换视图过渡是另一个 2023–2024 年间推出的革命性特性。它让同一个 DOM 在不同状态之间切换时例如页面跳转、列表重排可以产生流畅的过渡动画无需手动编写复杂的 DOM 状态管理。4.1 核心 API视图过渡的核心是document.startViewTransition()方法。当你调用它并传入一个更新 DOM 的回调时浏览器会捕获当前页面的快照截图。执行回调更新 DOM。捕获新状态的快照。在两个快照之间创建过渡动画默认是淡入淡出。functionspaNavigate(url){if(!document.startViewTransition){// 降级直接跳转window.location.hrefurl;return;}document.startViewTransition((){// 更新 DOM 到新页面状态updateDOMForPage(url);});}4.2 CSS 定制过渡效果你可以通过伪元素::view-transition-old和::view-transition-new自定义动画/* 默认效果淡入淡出 */::view-transition-old(root){animation:fade-out 0.3s ease;}::view-transition-new(root){animation:fade-in 0.3s ease;}更高级的用法为不同元素指定独立的过渡组。使用view-transition-name属性。.header{view-transition-name:header;}.sidebar{view-transition-name:sidebar;}然后就可以针对header和sidebar分别定义过渡动画比如让标题从左边飞入侧边栏从右边飞入。4.3 多页面应用MPA中的视图过渡2026 年Chrome 和 Firefox 已经支持跨文档视图过渡Cross-document View Transitions即在传统刷新页面式的导航中也能使用视图过渡。只需在link或meta中声明linkrelstylesheethrefstyle.cssmedianavigation/或通过 JS 启用window.addEventListener(pagereveal,(e){e.viewTransition.updateCallbackDone(){// 新页面已加载但未渲染};});4.4 实战图片画廊的平滑切换document.querySelectorAll(.thumbnail).forEach(thumb{thumb.addEventListener(click,async(e){constfullImagedocument.getElementById(full-image);constimageUrlthumb.dataset.large;if(document.startViewTransition){document.startViewTransition((){fullImage.srcimageUrl;});}else{fullImage.srcimageUrl;}});});配合 CSS 过渡可以实现大图从缩略图位置展开的效果。4.5 注意事项视图过渡会捕获页面截图对性能有一定影响不要滥用。某些元素如fixed定位的元素在过渡中可能表现异常需要调整view-transition-name或避免。早期版本有 bug2026 年已基本稳定。第五章综合实战 —— 一个完整的交互页面我们将使用以上所有技术构建一个产品展示页面头部导航条滚动时背景模糊、阴影加深过渡。页面加载时标题从上方淡入关键帧。滚动时每个产品卡片依次淡入视图进度动画。点击卡片展开详情视图视图过渡 关键帧。页面顶部显示阅读进度条滚动进度动画。由于代码量较大这里给出核心结构。headerclasssticky-header.../headerdivclassprogress-bar/divmainh1classpage-title我们的产品/h1divclasscard.../div.../main/* 头部滚动效果 */.sticky-header{transition:background-color 0.2s,box-shadow 0.2s;}.sticky-header.scrolled{background:rgba(255,255,255,0.95);box-shadow:0 2px 8pxrgba(0,0,0,0.1);}/* 进度条滚动动画 */.progress-bar{position:fixed;top:0;left:0;height:3px;background:#f90;animation:grow linear;animation-timeline:scroll();}keyframesgrow{from{width:0%;}to{width:100%;}}/* 卡片视图进度动画 */.card{animation:fadeUp linear;animation-timeline:view();animation-range:entry 0% entry 100%;}keyframesfadeUp{from{opacity:0;transform:translateY(30px);}to{opacity:1;transform:translateY(0);}}/* 页面标题入场关键帧 */.page-title{animation:slideDown 0.5s ease-out;}keyframesslideDown{from{opacity:0;transform:translateY(-20px);}to{opacity:1;transform:translateY(0);}}// 视图过渡点击卡片展开详情document.querySelectorAll(.card).forEach(card{card.addEventListener(click,(){constdetailscard.querySelector(.details);if(document.startViewTransition){document.startViewTransition((){details.classList.toggle(expanded);});}else{details.classList.toggle(expanded);}});});这个页面没有使用任何第三方库却实现了现代 Web 动效的最高水平。第六章性能优化与最佳实践6.1 坚持使用 transform 和 opacity这两个属性只需合成器线程参与不会触发重排或重绘。例如移动元素用translate淡入淡出用opacity缩放用scale。6.2 使用 will-change 提前告知浏览器如果某个元素即将发生动画可以加上will-change: transform;提示浏览器创建独立图层但不要滥用否则内存过高。6.3 减少动画层级动画元素应尽量少每一层动画都会增加 GPU 合成负担。6.4 利用requestAnimationFrame进行 JS 动画如果必须用 JS 控制动画使用requestAnimationFrame而非setTimeout保证与屏幕刷新率同步。6.5 优先使用 CSS 动画而非 JS只要能用 CSS 实现绝不写 JS。维护成本低性能好。6.6 避免动画导致页面跳字jank使用 Chrome DevTools 的 Performance 面板查看帧率、耗时长的任务。如果发现 Layout Shift尝试改为transform。第七章未来展望CSS 动画的未来不止于此。我们还将看到更精细的滚动驱动控制比如基于元素在视口中的位置比例而非简单的进入/离开。动画合成Animation Composition多个动画可以同时控制同一个属性类似 GSAP 的时间线。自定义贝塞尔曲线编辑更直观的缓动函数设计。视图过渡的进一步普及跨文档过渡成为默认甚至支持返回手势动画。让交互成为体验的灵魂从简单的悬停过渡到复杂的关键帧序列再到滚动驱动动画和视图过渡CSS 已经从一个“样式语言”进化为一个完整的交互设计平台。如今我们不再需要为了一个滚动视差而引入几十 KB 的 JS 库不再需要为了页面切换的流畅而手写状态管理。你可以用两行 CSS 实现滚动进度条用view()让卡片随着滚动渐入用startViewTransition实现原生 App 般的页面切换。动效不只是“花哨”它是用户理解界面的向导是品牌个性的表达是响应性的确认。在你的下一个项目中试着多给按钮加一个微妙的过渡给加载加一个旋转的动画给卡片加一个优雅的入场动效。你会发现用户会下意识地觉得“这个网站真流畅”。CSS 动画的世界值得我们不断探索。希望这篇文章能成为你进入这个世界的完整地图。