资讯动态

纯CSS卷轴展开特效:原理拆解+材质细节+避坑指南

发布时间:2026/10/20 3:58:32 来源:尧图企业网站定制
作为一个常年跟CSS动画打交道的人“卷轴打开”这种开场特效我做过好几个版本从一开始的简单遮罩平移到后来左右画轴缓缓退开、纸边还带卷筒纹理的版本中间踩了不少坑。这个效果在作品集首页、古风网站 banner、邀请函页面上都特别能撑场面——既不需要引入动效库也不依赖 Canvas纯 CSS 就能做出很接近真实卷轴打开的动作质感。今天这篇就直接给你一套我实测过的方案从视觉原理拆解到完整代码再到我踩过的四个典型问题。适合正在做落地页但不想上重型框架的人也适合想把 CSS 过渡、伪元素、合成层这些基础概念一次吃透的初学者。内容有点长但每一段都是干活时用得到的。1. 先想明白卷轴展开到底在动什么很多人一上手就写代码结果做着做着发现为什么打开之后内容的位置不对为什么轴杆跑到画面外去了为什么收起的时候纸和杆不同步这些问题的根源都出在同一个地方——没想清楚卷轴打开这个动作视觉上真正发生的是什么。1.1 从一张真实的画轴里拆出要素拿一幅传统卷轴来说你看到的其实就这几样东西一根左轴、一根右轴、卷在轴上的纸面以及被纸面盖住的内容。所谓“打开”就是两根轴带着纸分别向两侧滚开原本藏在纸下面的图文从中间逐渐露出来。把这段话翻译成 CSS 的视觉语言就得到三个独立可控制的对象内容层始终固定在最底层它就是我们要展示的图文本身。纸层覆盖在内容上方左右各占一半初始状态完全遮挡住内容打开时向左和向右平移退场。轴杆层一根长条形的木轴跟随纸层一起移动让观众看着像“纸是从轴上被抽开的”。这三个对象的层次关系非常明确内容在最下面纸层在中间轴杆浮在纸层和内容之上。理解了这一点后面所有代码就都围绕它展开。1.2 为什么不能只用一张白布做平移动画我看到新手最容易做的版本是在内容上面盖一张不透明的矩形然后让它从左往右移开露出内容。这确实能产生“东西露出来了”的效果但它更像皮影戏的幕布被拉开一点都不像卷轴。差别在哪里真实卷轴打开时有几个关键视觉特征第一两根轴是从中间向两侧分开的不是单向拉开一个幕布第二纸不是平整地消失而是带着卷曲的厚度感退场第三轴的材质是圆柱体有高光和暗面。所以我的方案是左右双纸层对称退场而不是单层遮罩平移。左右各盖一层打开时左纸层向左 translateX(-100%)右纸层向右 translateX(100%)视觉中心始终保持在画面中线上。这样观众第一眼看过去注意力会落在从中间逐渐露出的内容上这正是“卷轴打开”最勾人的那个瞬间。1.3 两个方案里我为啥选了遮罩式而不是复制式还有一条路是左右各复制一份相同的内容做成两个视窗各自向两侧移动。这个方案视觉效果可控但有一个明显的缺陷同一份内容要在 DOM 里渲染两遍对于纯文本倒无所谓如果内容里有图片、视频、复杂表单就白白多了一倍渲染成本而且浏览器开发者工具里调试时看着两份一模一样的节点很烦。我最终用的是“内容固定 纸层遮挡”的方案。内容只渲染一次固定不动左右两个纸层负责遮挡和退场。这样做 DOM 结构短、层级干净、性能也更好。唯一的代价是纸层离开后露出的内容是原本就存在的少了一点“内容被画出来”的过程感。但对于 95% 的展示场景这种视觉误导已经足够真实观众根本不会去追究纸是从哪里展开来的。2. 动手实现纯 CSS 卷轴展开的核心结构思路理顺之后代码其实不难。整个实现下来核心 CSS 加起来一百行左右没有 JavaScript 也能完成展开动画我之所以加了按钮只是为了方便演示来回切换。2.1 先把 HTML 骨架搭出来div classscroll-scene idscrollScene div classscroll-content div classscroll-inner span classscroll-kicker锦 绣 河 山/span h2春风得意马蹄疾br一日看尽长安花/h2 p这是一个用 CSS 实现的卷轴打开特效示例/p /div /div div classscroll-paper scroll-paper--left div classscroll-roller/div /div div classscroll-paper scroll-paper--right div classscroll-roller/div /div button classscroll-btn idscrollBtn typebutton展开卷轴/button /div注意我把滚动轴杆.scroll-roller放进了.scroll-paper里面这是最初几版踩坑之后改的。轴杆如果独立放在纸层外面动画结束后想让它和纸停在同样位置就得手动计算一个像素级位移值容器宽度一变就得改放进纸层里纸怎么走轴就怎么走永远对准。2.2 核心 CSS定位、层级和过渡.scroll-scene { position: relative; width: min(720px, 92vw); height: 480px; margin: 2rem auto; overflow: hidden; border-radius: 6px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18); } .scroll-content, .scroll-paper { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .scroll-content { /* 放在纸层下面始终固定 */ z-index: 1; } .scroll-paper { /* 盖住内容左右各占一半 */ z-index: 3; transition: transform 1.7s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.4s ease; will-change: transform; } .scroll-paper--left { width: 50%; } .scroll-paper--right { left: 50%; width: 50%; } /* 展开状态纸层向两侧退开 */ .scroll-scene.open .scroll-paper--left { transform: translateX(-100%); } .scroll-scene.open .scroll-paper--right { transform: translateX(100%); }这里有几个点值得展开说。第一是transition我写在纸层的默认状态上而不是.open状态里。这样展开和收起会用同一组参数反向执行时自动对称不会出现“展开很顺、收起很生硬”的问题。第二是时间曲线选用了cubic-bezier(0.65, 0, 0.35, 1)。这组参数的曲线形态是两头略缓、中间稍快接近一个有一定重量的物体在惯性下滚动的感觉。真拿直线匀速 linear 做纸层就像在冰面上滑没有阻尼感。你可以打开浏览器开发者工具把这个贝塞尔曲线改成其他值对比一下区别很大。第三是will-change: transform。它提示浏览器提前给纸层分配合成层动画期间不用反复重新创建实测在低端机上的掉帧明显减少。但这个属性不要滥用动画结束后最好移除否则页面里常驻太多合成层反而占内存。如果你只是做一次性展示不追求极致也可以先不加后面性能章节我再细说。2.3 轴杆和纸边的卷筒细节接下来是轴杆的位置和造型。轴杆本身是一个垂直的长圆条因为放在半个纸层里所以初始位置要贴在纸层靠中线的那条边上。.scroll-roller { position: absolute; top: 0; width: 34px; height: 100%; z-index: 5; border-radius: 17px; background: linear-gradient( 90deg, #5b3417 0%, #8a4c22 18%, #b5783f 35%, #8a4c22 55%, #5b3417 75%, #7c401e 100% ); box-shadow: inset 0 0 4px rgba(255, 255, 255, 0.15), inset 0 0 8px rgba(0, 0, 0, 0.25), 0 4px 12px rgba(0, 0, 0, 0.28); } .scroll-paper--left .scroll-roller { right: -17px; } .scroll-paper--right .scroll-roller { left: -17px; }位置解释一下左纸层的轴杆放在右边缘并且right: -17px意味着轴杆中心正好压在纸层的中缝上右纸层的轴杆放在左边缘left: -17px中心同样压在中缝上。两个轴杆在中缝处几乎贴在一起看起来就像一根完整的粗轴。轴杆的渐变我写了六个色标从外到内依次是深棕、中棕、亮棕、中棕、深棕、中棕。这正是用 CSS 模拟圆柱体的技巧边缘深中间靠近光源的 35% 位置最亮右侧再加一层深色形成背光。没有这一层渐变轴杆看起来就是块平板很假。再补一个卷筒纹理。纸层靠近中线的位置加一条彩色渐变模拟纸反复卷绕形成的圆圈层叠感.scroll-paper--left::after, .scroll-paper--right::after { content: ; position: absolute; top: 0; width: 28px; height: 100%; background: repeating-linear-gradient( 90deg, #eadbc2 0 2px, #d6c4a6 2px 4px ); } .scroll-paper--left::after { right: 0; box-shadow: inset -6px 0 6px -3px rgba(0, 0, 0, 0.25); } .scroll-paper--right::after { left: 0; box-shadow: inset 6px 0 6px -3px rgba(0, 0, 0, 0.25); }这组细条纹不用太规则2px 一格就行。离远看它就是轴杆旁边一卷厚纸的层叠截面整个视觉可信度一下子提高不少。2.4 收尾按钮切换的逻辑为了让演示完整加一个切换按钮上去用一行 JavaScript 切换 CSS 类名即可。button classscroll-btn idscrollBtn typebutton展开卷轴/buttonconst scene document.getElementById(scrollScene); const btn document.getElementById(scrollBtn); btn.addEventListener(click, () { const opened scene.classList.toggle(open); btn.textContent opened ? 收起卷轴 : 展开卷轴; });按钮样式随意注意给按钮一个合适的z-index让它始终浮在纸层上方否则展开之后按钮会被纸层盖住点不到。我这里的做法是给.scroll-btn设置z-index: 10且绝对定位在场景底部中间。3. 让“木轴”更像木轴材质与光影细节第一版跑通之后整体效果是能看了但总感觉差了点什么。后来我把截图放大逐帧看发现问题在于纸面和轴杆都太干净了。真实的老卷轴有质感变化有边缘磨损有轴头有细微的木纹。这些东西单看不明显但拼在一起就是“真”和“像网页模板”的分界线。3.1 轴头给长杆补上两个圆帽圆柱杆做出来后两端是平的视觉上像根水管。真实的木轴两端会有突出来的圆形轴头我直接用伪元素解决.scroll-roller::before, .scroll-roller::after { content: ; position: absolute; left: 50%; width: 46px; height: 46px; border-radius: 50%; transform: translateX(-50%); background: radial-gradient( circle at 35% 35%, #a56535 0%, #6b391a 55%, #3f2110 100% ); box-shadow: inset 0 -2px 5px rgba(0, 0, 0, 0.35), inset 0 2px 4px rgba(255, 255, 255, 0.2); } .scroll-roller::before { top: -8px; } .scroll-roller::after { bottom: -8px; }圆球轴头的渐变用radial-gradient(circle at 35% 35%, ...)模拟光源从左上打过来时球体高光偏左上、暗部偏右下。这两个圆帽会让轴杆一下子有了“这是实物”的体量感。3.2 纸面做旧一篇干净的文章怎么变“老”纸层和内容层我用了一个共同的暖色渐变基底让展开前后的纸张颜色完全连续左侧偏亮、右侧偏暗颜色过渡得非常缓和。.scroll-content, .scroll-paper { background: linear-gradient(105deg, #f4ecdf 0%, #faf4e8 45%, #eee3d0 100%); }在此基础上我又给纸层加了一层细微的内阴影模拟纸张边缘的氧化发暗.scroll-paper--left { box-shadow: inset -14px 0 18px -10px rgba(74, 54, 32, 0.35), inset 0 0 50px rgba(74, 54, 32, 0.08); } .scroll-paper--right { box-shadow: inset 14px 0 18px -10px rgba(74, 54, 32, 0.35), inset 0 0 50px rgba(74, 54, 32, 0.08); }注意阴影方向左纸层的暗边缘在右侧因为左半张纸靠近中缝的地方是被卷进去的光线照不到右纸层的暗边缘在左侧同理。如果你追求更重的做旧感可以在这套渐变后面叠一层噪点图比如一张很小的 PNG 噪点纹理通过background-blend-mode: multiply混合进去。我常用的做法是background: url(noise.png), linear-gradient(105deg, #f4ecdf 0%, #faf4e8 45%, #eee3d0 100%); background-blend-mode: multiply;噪点纹理的透明度和混合模式要自己调通常opacity: 0.06左右最安全太浓会显得脏。3.3 光感统一页面里所有阴影都要讲同一个光源故事一个很容易被忽略的细节是光源方向。我整套 CSS 都假设光从左上角来所以轴杆的高光在 35% 位置靠左轴头高光偏左上左纸层的边缘暗影在右右纸层的边缘暗影在左。只要其中任何一个物体把光源改到右边整个画面就会有一种“光源乱跳”的违和感。这个原则同样适用于外层场景阴影。我给.scroll-scene用的box-shadow: 0 12px 40px rgba(0,0,0,0.18)阴影往下偏因为光在上方。如果做挂在墙上的卷轴效果还要加一层投影到下方墙面形成悬浮感。光影逻辑统一是成品和 demo 之间一条非常清晰的界线。4. 优化与避坑实测中遇到的四个典型问题这部分是实打实的实战记录。以下四个问题我至少各碰到过一次有的花了一晚上排查印象很深。你如果在自己的实现里撞见同样的现象可以直接对着这里排查。4.1 轴杆和纸层不同步越移越对不上最初版本里轴杆是独立在.scroll-paper外面的我图省事直接给轴杆写了transition: left 1.7s打开时把left从 50% 改成 5%结果一旦场景宽度变化轴杆停的位置就不对纸已经出画面了轴还卡在中间附近。后来我把轴杆移入纸层内部就不再需要单独计算位移。如果你确实想把轴杆独立出来比如想给它加单独的惯性动画那就用 transform 而不是 left并且在 JS 里动态测量纸层的像素宽度再计算目标偏移。我的建议是常规场景没必要放进纸层里最稳。4.2 transform 被叠加动画过程出现突然跳动有一次我把过渡加在了.scroll-paper的通用样式上又在某个子选择器里给同一个元素同时设置了transform和位移属性。浏览器最后只认后一个 transform导致展开时纸层直接从起点跳到终点完全没有过渡过程。排查技巧是打开开发者工具选中纸层元素在 Computed 面板里看 transform 矩阵到底是谁生效。如果有多个规则写在同一优先级就把 transition 相关代码合并到同一个选择器里。我这里最终把所有纸层过渡参数放在.scroll-paper一个地方避免后续状态覆盖。4.3 动画结束后轴杆盖住了文字没法点卷轴打开后轴杆停在场景左右外侧看起来没什么问题。但我在实际点击内容里的按钮时发现轴杆占了 34px 宽即使它已经到了场景边缘之外只要它还在元素流里就会拦截那一片区域的鼠标事件。原因是我给轴杆设了z-index: 5它的层级比内容层高。解决方式是在动画结束后把轴杆的pointer-events: none或者把轴杆的z-index在.open状态下显式降到 1.scroll-scene.open .scroll-roller { pointer-events: none; z-index: 1; }另外别忘了设置scroll-scene的overflow: hidden让移到外面的轴杆不会撑破容器产生横向滚动条。我都吃过这个亏。4.4 窗口尺寸变化后半区卡在 50% 导致两侧对不齐我用width: 50%定义左右纸层宽度看起来天经地义但在连续动画过程中如果用户改变浏览器窗口宽度左右纸层之间的中缝可能因为像素取整出现一两个像素的重叠或露白。屏幕缩放比例不是整数时50%会四舍五入我见过左纸层宽 359px右纸层宽 360px 的情况中间直接漏出一条背景色细线。处理办法有两个。一个是给左右纸层设一层极窄的重叠区比如让左纸层width: calc(50% 1px)右纸层保持 50%轻微重叠不会被肉眼发现但能挡住露白。另一个是干脆用display: grid; grid-template-columns: 1fr 1fr去布局由 CSS 计算引擎处理分栏像素取整问题会轻很多。4.5 补充不支持的浏览器和关闭动画偏好这个特效依赖 transform 和 transition现代浏览器基本都没问题但有两个边界情况要考虑。降级方案用一个supports就能兜住supports not (transform: translateX(1px)) { .scroll-paper { display: none; } }不支持 transform 的浏览器直接跳过动画把内容露出来比卡在半透明状态好得多。另外很多操作系统有“减少动态效果”的辅助功能选项。我在项目里通常会加media (prefers-reduced-motion: reduce) { .scroll-paper { transition-duration: 0.01ms; opacity: 0; } }这样对动效敏感的用户直接看到完整内容互相不伤害。这不算什么炫技但到了真实项目里很加分。5. 性能与使用建议把特效放进真实项目如果你只是做一个演示页面前面几章的代码足够了。但要把卷轴特效放进真实项目有几个细节我建议你再花点时间处理。5.1 动画期间只动 transform 和 opacityCSS 动画性能最基本的一条就是只动 transform 和 opacity不要动 left、top、width、margin 这些会触发布局和绘制的属性。卷轴特效没有任何布局变化纯粹是纸层和轴杆的位移这是它性能友好的先天优势。动画期间浏览器能把纸层、轴杆提升到独立合成层不牵动整页重排。如果要更进一步可以在动画前后控制will-change。实现方式是给.scroll-scene.open下的纸层加上动画结束后通过 transitionend 监听移除。不过这个优化对短期动画的收益很有限项目里真追求丝滑优先保证两点不要同时让很多大面积的层做模糊滤镜移动端不要开 2 倍以上的阴影。5.2 长内容怎么办如果卷轴里要放很长一段文字你可以把.scroll-content做成内部可滚动容器.scroll-content { overflow-y: auto; scrollbar-width: none; } .scroll-content::-webkit-scrollbar { display: none; }隐藏滚动条是为了不破坏古风视觉。展开后观众可以直接在里面滚动内容纸层已经移开不会遮挡交互。不过要注意滚动事件会发生在.scroll-content上外层页面不会跟着滚。这里需要你在实际布局里判断是否要让嵌套滚动如果页面上还有别的内容建议做成弹层式展开而不是内嵌一段会抢滚动的小容器。5.3 什么时候该用这套什么时候别用最后直接一点说结论卷轴特效适合做“一次性开场”或者“阶段性展示”不适合出现在需要频繁来回切换的交互中。原因很简单整个动画最长 1.7 秒如果用户反复点按钮纸层一开一合长时间看会累。如果是支付页、注册引导这种追求快捷的任务流强烈建议不要加这种花哨动效用户没耐心等。尽自己所能把动效放在有仪式感的节点它的价值才能被看清楚。个人经验上这套特效做得最值的一次是给一个文化类网站做首页首屏卷轴打开之后露出核心文字接着整页从暖色过渡到暗色气氛一下就出来了。后来我把同一个组件抽成独立区块换掉内容区文字和纸色渐变用在朋友的一个电子邀请函上只是把展开后的内容换成了时间和地址收起改成关闭邀请函整体依然成立。这也是这套方案的扩展性所在——核心结构稳定之后内容区和纸层颜色独立调整即可不需要改动动效逻辑。最后如果你也想自己试一版我的建议是从最简结构开始先把左右纸层“移开、合上”这个动作跑通再加轴杆、轴头、纸边纹理。一次只加一个小细节出了问题也容易定位。真正做完之后你会感受到CSS 动画的价值不在代码多少而在对现实动作观察得够不够细。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑