第26章 滚动属性
26 分钟阅读
第二十六章:滚动相关属性
想象一下,你在网上阅读一篇长文章,滚动鼠标滚轮时,页面像蜗牛一样卡顿,你会不会直接关掉网页?滚动体验直接影响用户的浏览感受。这一章我们就来学习如何让页面滚动丝滑如德芙巧克力!
26.1 平滑滚动
26.1.1 scroll-behavior: smooth——让锚点跳转和 scrollIntoView 方法平滑滚动
scroll-behavior 是 CSS 滚动体验的第一步。当你点击一个锚点链接时,页面是瞬间跳过去的,还是"滑"过去的?smooth 就是让滚动像坐滑梯一样平滑。
什么是平滑滚动?
想象你坐滑梯,顶部直接跳到地面(instant)和坐滑梯滑下去(smooth),你更喜欢哪个体验?smooth 就是 CSS 给你的"滑梯"。
| |
| |
scroll-behavior 的应用场景:
| |
📌 四条必须知道的规则(都来自规范原文):
- 它只管"非用户发起"的滚动:锚点跳转、
scrollIntoView()/scrollTo()等滚动 API、以及自动的吸附修正会变平滑;你手动滚鼠标滚轮、手指滑动不受影响。- 它是给"滚动容器"用的:写在
html上会应用到视口;写在某个overflow: auto的盒子上,只影响那个盒子。- 它不会从
<body>传播到视口(这一点和overflow不一样)。所以想全局平滑就写在html上,写在body上不保证生效。scroll-behavior不会被子元素继承,每个滚动容器各管各的。
无障碍提醒:一定要照顾"晕动症"用户。 平滑滚动、大幅度视差动画会让部分用户眩晕,规范也明确允许浏览器忽略这个属性。标准做法是用 prefers-reduced-motion 让用户在系统里关掉动画时自动退回"瞬间跳转":
| |
浏览器支持: Chrome / Edge 61+、Firefox 36+、Safari 15.4+。Safari 在 15.4 之前完全不认这个属性,写上去也不会报错,只是"没效果"。
26.1.2 scrollIntoView()——JavaScript 方法,element.scrollIntoView({ behavior: ‘smooth’ })
scrollIntoView 是一个 JavaScript 方法,可以编程控制滚动到指定元素。配合 behavior: 'smooth' 参数就能实现平滑滚动。
| |
📋 参数默认值速查(来自 CSSOM View 规范):
参数 默认值 可选值 behavior'auto''auto'/'instant'/'smooth'block'start''start'/'center'/'end'/'nearest'inline'nearest'同上
behavior: 'auto'的真正含义是"看 CSS 怎么说":目标元素的计算scroll-behavior是smooth就平滑,否则瞬间;而'instant'是"不管 CSS 说什么,都给我瞬间到位"。
| |
scrollIntoView 的兼容性写法:
| |
26.2 自定义滚动条
26.2.1 ::-webkit-scrollbar——webkit 内核浏览器的滚动条整体样式
自定义滚动条是提升网页质感的重要细节。默认的滚动条又粗又丑,在某些设计风格下显得格格不入(比如精致的暗色主题),::-webkit-scrollbar 让你可以把它打扮得漂漂亮亮——换色、圆角、缩放,一个都不落下。
什么是 ::-webkit-scrollbar?
滚动条由几部分组成:::-webkit-scrollbar 是整体样式,::-webkit-scrollbar-track 是轨道(背景),::-webkit-scrollbar-thumb 是滑块(可以拖动的部分)。
| |
| |
::-webkit-scrollbar 的三个实用须知:
| 须知 | 说明 |
|---|---|
| 只在 WebKit / Blink 生效 | Chrome、Edge、Safari、各类国产套壳浏览器可以;Firefox 完全不认,写了也没用 |
一旦给 ::-webkit-scrollbar 设了宽高 | macOS 上原本"悬浮半透明"的滚动条会变成常驻的经典滚动条(占位置、不自动隐藏)。改动滚动条样式经常顺带改变了"什么时候出现",设计时要注意 |
| 和标准属性冲突 | 前面的 scrollbar-width / scrollbar-color 一旦写了非 auto 值,Chromium / Safari 就会忽略 ::-webkit-scrollbar |
同一家族的其他伪元素(都可以用,但支持同样只在 WebKit / Blink):
| |
🧠 一个冷知识:CSS 工作组和 WebKit 团队都公开表示,当年把
::-webkit-scrollbar这组私有伪元素暴露给 Web 是个"错误"(规范原文写的是 “considered a mistake by both the CSS Working Group and Webkit”)。原因正是上面那条——滚动条的内部结构由操作系统决定,各平台差异太大,暴露细节必然导致"在某些平台上好看、在另一些平台上崩"。所以才有了后来的标准属性scrollbar-width/scrollbar-color。
26.2.2 ::-webkit-scrollbar-track——滚动条轨道(背景)
轨道是滚动条的"滑道",滑块在上面滑动。
| |
26.2.3 ::-webkit-scrollbar-thumb——滚动条滑块(可拖动部分)
滑块是用户可以拖动来控制滚动位置的"把手"。
| |
26.2.4 scrollbar-width——滚动条宽度(标准属性)
Firefox 一直不支持 ::-webkit-scrollbar,所以它和 Chromium 一起推动了标准属性 scrollbar-width / scrollbar-color。这两个属性现在已经是通用写法,不再是"Firefox 专属":
| 属性 | Chrome / Edge | Firefox | Safari |
|---|---|---|---|
scrollbar-width | 121+ | 64+ | 18.2+ |
scrollbar-color | 121+ | 64+ | 26.2+ |
| |
⚠️ 最重要的一条:
scrollbar-width/scrollbar-color与::-webkit-scrollbar是"二选一"关系。在 Chrome 121+ / Safari 18.2+ 里,只要
scrollbar-width或scrollbar-color取了非auto的值,浏览器就会忽略::-webkit-scrollbar系列的样式,改用标准属性去画滚动条。反过来,如果只写::-webkit-scrollbar,在 Firefox 里则完全没效果。所以"两套都写"不是叠加,而是互相打架——旧版教程里常见的下面这种写法,在 Chrome 121+ 上会以
scrollbar-width为准:
1 2 3 4 5 6 7 8 9/* ⚠️ 结果不确定:新旧两套同时生效时,Chromium 优先用标准属性 */ .conflict { scrollbar-width: thin; /* 这行会让下面的 ::-webkit-scrollbar 失效 */ scrollbar-color: red yellow; /* 这行也会 */ } .conflict::-webkit-scrollbar { width: 12px; /* 在旧 Chromium 上生效,在新版上可能被忽略 */ }实践建议:
- 只需要"细一点 / 换个颜色"→ 只用
scrollbar-width+scrollbar-color,简单、跨浏览器;- 需要"圆角、渐变、hover 变色"这类精细控制 → 只用
::-webkit-scrollbar(接受 Firefox 里是默认样式)。
scrollbar-width: none 与"隐藏滚动条"的正确姿势:
| |
26.2.5 scrollbar-color——scrollbar-color: thumb-color track-color
scrollbar-color 的语法就是"滑块颜色 轨道颜色",顺序不能反。它的初始值是 auto(跟随系统/UA),也可以只指定滑块颜色、轨道写 auto。
| |
📌 两个容易忽略的细节:
scrollbar-color是"继承属性"(scrollbar-width不是)。所以写在容器上,内部滚动区域也会跟着变色;而scrollbar-width必须逐个滚动容器写。- 写在根元素上时会应用到视口,但和
overflow不同:写在<body>上的scrollbar-color不会传播到视口。另外,规范还顺带提了一句心态问题:滚动条是操作系统级的交互控件,
scrollbar-width的定位是"给紧凑的小区域用细滚动条",而不是"让每个网站都长出自己风格的滚动条"——用户可以在系统设置里覆盖它,浏览器也可以直接忽略。所以别把自定义滚动条当成"必须生效"的设计细节。
26.3 滚动快照
26.3.1 scroll-snap-type——x(水平吸附)/ y(垂直吸附)/ block / inline / both / mandatory(强制吸附)/ proximity(接近才吸附)
滚动快照(Scroll Snap)是一种"强制的优雅"。当用户滚动停止时,页面会自动吸附到某个"快照点",让滚动有一个明确的停止位置,而不是随意停在任何地方。
什么是滚动快照?
想象你翻杂志,翻一页是一整页,不会停在半页中间——那太难受了对吧?滚动快照就是这个效果——页面滚动时会自动"翻页"到完整的内容区块。用户松手后,CSS 帮你决定停在哪,不用担心停在尴尬的位置。
| |
| |
一个能直接跑起来的完整例子(横向幻灯片):
| |
| |
⚠️
mandatory的经典翻车点:内容会变得"滚不到"。 规范里有两条相关说明:
- 吸附项比容器大时(比如一张图比视口还高),规范要求"只要这个吸附区域还盖满整个滚动视口,任意滚动位置都算有效吸附位置"——所以现代浏览器允许你在这一项内部自由滚动,不会死锁。
- 真正危险的是"吸附点稀疏"的情况:如果吸附区域之间隔着很大的空隙,中间的内容就可能永远滚不出来。规范举的例子特别典型——把
mandatory加在每一节的标题上(而不是整节),结果第一节和最后一节的一部分内容就再也看不到了。规范原话是:“这就是为什么不该把强制吸附点放在可能相距很远、彼此不挨着的元素上。”实践判断标准:只有当每个吸附项都能完整展示、且吸附项彼此相邻时,才用
mandatory;内容长度不可控(比如文章正文)时请用proximity,或者干脆不吸附。这也是很多"全屏滚动页面在手机上滚不动"的根源。💡 两个补充:
- 吸附只认"带
scroll-snap-align的后代盒子",不要求是直接子元素,但通常写成直接子元素最直观;scroll-snap-type与scroll-behavior: smooth是"黄金搭档":吸附负责"停在哪",平滑负责"怎么过去"。
26.3.2 scroll-snap-align——start(吸附到起点)/ end(吸附到终点)/ center(吸附到中心)
scroll-snap-align 决定内容在吸附时的对齐方式。
| |
💡 三个取值怎么选?
start:最常用。卡片/幻灯片从左上角开始对齐,符合"从头读"的直觉;center:适合图片画廊,让图片居中、左右两侧露出邻居的一小条,暗示"还能继续滑";end:让内容末尾对齐容器末尾,常见于"从右往左"的布局或最后一个项目要贴边的情况。
26.3.3 scroll-snap-stop——normal(默认,可跳过吸附点)/ always(必须停在每个吸附点)
scroll-snap-stop 控制"一次滑动/滚轮能不能越过多个吸附点"。
| |
📌 细节补充:
always挡的是"一次手势内越过多个吸附点",不会让锚点跳转、scrollIntoView之类的一次性定位被拦住;- 支持情况:Chrome / Edge 75+、Firefox 103+、Safari 15+。在此之前 Safari 只认
normal的效果(写always会被忽略),所以 “必须一张一张翻” 的需求要有兜底意识(比如用 JS 处理滚动按钮)。
26.4 滚动内边距与外边距
26.4.1 scroll-padding——滚动容器的内边距,影响 scroll-snap 的吸附位置
scroll-padding 让你可以在滚动容器内部留出一块"缓冲区",这样吸附点就不会紧贴容器边缘。
| |
⚠️
scroll-padding不是padding。 它不改内容布局、不把内容往里挤,而是调整"滚动的参照框(snapport)"——也就是告诉浏览器:“判断’滚到位了没有’的时候,把容器上下左右各削掉这么多”。所以它既能修正吸附位置,也能修正scrollIntoView()和锚点跳转的落点。💡 它最出名的用途:配合
position: sticky的固定头部。 有吸顶导航时,点锚点跳过去,标题往往被导航盖住——scroll-padding-top就是官方解法,不需要再给每个标题加scroll-margin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14:root { --header-height: 64px; } html { scroll-padding-top: var(--header-height); /* 锚点跳转时不钻到吸顶导航下面 */ scroll-behavior: smooth; } header { position: sticky; top: 0; height: var(--header-height); }
scroll-padding 的完整长属性:
| 长属性 | 说明 |
|---|---|
scroll-padding-top / right / bottom / left | 物理方向 |
scroll-padding-block / scroll-padding-inline | 逻辑方向(跟随书写模式) |
scroll-padding-block-start 等 | 更细的逻辑方向长属性 |
📌 浏览器支持:Chrome / Edge 69+、Firefox 68+、Safari 14.1+。Safari 11~14 之间存在一个坑(WebKit bug 179379):
scroll-padding对"跳到锚点"和scrollIntoView()不生效,只对吸附生效。如果需要兼容这批老 Safari,可以改用给目标元素加scroll-margin,或者用 JS 手动偏移。
26.4.2 scroll-margin——滚动项目的外部边距,同样影响吸附计算
scroll-margin 是在滚动项目本身上设置的,影响吸附位置的计算。
| |
💡
scroll-margin和scroll-padding的区别,一句话就够:
写在哪 类比 适合 scroll-padding滚动容器上 给容器"内缩一圈" 吸顶导航、统一给所有锚点留白 scroll-margin滚动的目标元素上 给元素"外扩一圈" 只给某几个元素留白、表单聚焦时留出轮廓空间 两者效果类似(都会改变最终滚动位置),同时使用时相加。
scroll-margin还有两个实用场景:
- 表单聚焦:
input:focus用element.focus()滚进视野时,配合scroll-margin-top可以避免输入框贴着视口边缘;- 焦点轮廓:
scroll-margin给:focus-visible的光圈留出空间,避免轮廓线被容器裁掉。它同样有物理/逻辑长属性(
scroll-margin-top、scroll-margin-block-start等),浏览器支持与scroll-padding相同(Safari 14.1 之前叫scroll-snap-margin,且同样不作用于锚点跳转)。
26.5 scrollbar-gutter
26.5.1 scrollbar-gutter——让"滚动条占的位置"变得可预测
经典滚动条会占宽度。于是就有了那个经典现象:从"内容少、没滚动条"的页面点进"内容多、有滚动条"的页面时,整个页面的内容会突然向左挪几像素,像抖了一下。这类因滚动条出现/消失造成的"布局抖动(layout shift)",正是 scrollbar-gutter 要解决的问题。
它的完整取值只有三档:
| 取值 | 含义 |
|---|---|
auto | 初始值。滚动条出现时才占位置(overflow: auto 溢出时;overflow: scroll 则始终占位) |
stable | 只要 overflow 是 hidden / scroll / auto,不管内容是否溢出都预留那一块空间 |
stable both-edges | 同上,并且两侧对称预留(居中布局不会因为滚动条跑到一边而偏斜) |
| |
📌 五条关键细节:
- 只对"滚动容器"生效(规范:Applies to: scroll containers),普通
display: block的盒子写了也没用;- 它只管"留不留位置",不管"滚动条显不显示"——
stable不会让滚动条常驻,只是把它的坑位留下;- 写在根元素上会应用到视口(和
overflow一样),但和overflow不同的是:它不会从<body>传播到视口,所以全局设置要写在html上;- 对"覆盖式滚动条"(overlay scrollbars)没有效果。 macOS / iOS 默认的悬浮滚动条不占空间,所以规范规定此时不存在滚动条沟槽——你在 Mac 上用 Safari 试这个属性,很可能"看不到任何变化",但换成 Windows 或把系统设置改成"始终显示滚动条"就能看到;
- 别再用
overflow-y: scroll硬凑了。 老办法是靠"永远显示滚动条"来消除抖动,代价是没内容时也常驻一条空滚动条;scrollbar-gutter: stable才是为这个需求设计的解法。浏览器支持: Chrome / Edge 94+、Firefox 97+、Safari 18.2+。Safari 的支持来得比较晚,这也是它相对"冷门"的原因之一。
26.6 overscroll-behavior
26.6.1 滚动链控制:auto(默认)/ contain(禁止滚动链)/ none(连"弹性/回弹"也一并禁掉)
滚动链(Scroll Chaining)是一个"爱管闲事"的行为——当一个滚动区域滚到底了,继续滚动竟然还会带动外部父容器一起滚。听起来很贴心,但在嵌套滚动场景下,这种"贴心"往往让人崩溃。
| |
三个取值到底差在哪?(依据规范原文)
| 取值 | 阻止"滚动链"(把滚动传给父容器) | 阻止"本地 overscroll 反馈"(回弹、发光、下拉刷新等) |
|---|---|---|
auto(初始值) | ✗ 不阻止 | ✗ 不阻止 |
contain | ✓ 阻止 | ✗ 不阻止 |
none | ✓ 阻止 | ✓ 阻止 |
所以
none=contain的效果 再叠加“连弹性反馈也不要”。想只解决"外层页面跟着乱滚"就用contain;想做无限滚动(滚到底自己加载更多,不希望触发下拉刷新)才用none。⚠️ 必须是滚动容器。 规范明确写着:不是滚动容器的元素会"接受但忽略"这个属性的值。另外,较早的浏览器实现(Chrome 144 之前、Firefox 150 之前)还要求这个容器真的有可滚动的内容,否则属性无效——所以给一个"内容没溢出的盒子"加
overscroll-behavior是白费力。💡 浏览器支持: Chrome / Edge 63+、Firefox 59+、Safari 16+。
26.6.2 overscroll-behavior-x / overscroll-behavior-y——分别控制水平和垂直方向
| |
overscroll-behavior 是上面两个长属性的缩写,规则和 overflow 一样:写一个值表示横竖都用,写两个值按"先 x 后 y"的顺序分别指定。
| |
💡 最经典的两个实战场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15/* 场景 1:弹窗/抽屉打开时,背景页面不要跟着滚 */ body.modal-open { overflow: hidden; } .modal-scroll-area { overflow-y: auto; overscroll-behavior: contain; /* 弹窗内容滚到底,别把背景页面带走 */ } /* 场景 2:手机上的"无限滚动"列表,防止下拉刷新打断浏览 */ .infinite-list { overflow-y: auto; overscroll-behavior-y: none; /* 既不让父容器滚,也不触发下拉刷新 */ }
本章小结
核心知识点
| 属性 | 说明 |
|---|---|
| scroll-behavior | 平滑滚动(只影响锚点/API 滚动,要照顾 prefers-reduced-motion) |
| scrollIntoView() / scrollTo() | JavaScript 滚动 API,behavior: 'auto' / 'instant' / 'smooth' |
| ::-webkit-scrollbar | 自定义滚动条(仅 WebKit / Blink) |
| scrollbar-width / scrollbar-color | 自定义滚动条(标准属性,与上面二选一) |
| scroll-snap-type | 滚动吸附的轴向与严格度 |
| scroll-snap-align | 吸附项的对齐方式 |
| scroll-snap-stop | 能否"一次滑过多张" |
| scroll-padding | 滚动容器内的"参照框内缩",锚点跳转与吸附都受益 |
| scroll-margin | 目标元素的外扩边距,与 scroll-padding 互补 |
| scrollbar-gutter | 滚动条沟槽占位,避免布局抖动 |
| overscroll-behavior | 滚动链与回弹效果控制 |
滚动体验要素
graph TD
A["滚动体验要素"] --> B["速度"]
A --> C["平滑度"]
A --> D["外观"]
A --> E["吸附"]
A --> F["边界行为"]
B --> B1["scroll-behavior 控制快慢方式"]
C --> C1["prefers-reduced-motion 尊重用户偏好"]
D --> D1["scrollbar-width / scrollbar-color"]
D --> D2["::-webkit-scrollbar(二选一)"]
E --> E1["scroll-snap-type / align / stop"]
E --> E2["scroll-padding / scroll-margin 修正落点"]
F --> F1["overscroll-behavior 控制滚动链"]
F --> F2["scrollbar-gutter 控制占位抖动"]
style A fill:#f39c12,stroke:#333,stroke-width:3px本章易错点速查
| 容易写错的地方 | 正确认识 |
|---|---|
给按钮元素写 scroll-behavior: smooth 就能平滑 | 它只对滚动容器生效(视口对应根元素 html),写在按钮上无效 |
scroll-behavior 能让鼠标滚轮变平滑 | 不对。它只管锚点跳转、滚动 API、自动吸附修正;用户手动滚动不受影响 |
写在 <body> 上就能全局生效 | 它不会从 body 传播到视口(overflow 才会),请写在 html 上 |
| 平滑滚动越多越好 | 忽视 prefers-reduced-motion 会让晕动症用户体验很差,规范也允许浏览器直接忽略该属性 |
scrollIntoView({ behavior: 'auto' }) 一定是瞬间跳 | auto 是"听 CSS 的":CSS 里是 smooth 就平滑。要强制瞬间要用 'instant' |
window.pageYOffset 是新写法 | 它是 window.scrollY 的旧别名,已不推荐使用 |
用 document.body.scrollIntoView() 回到顶部 | 标准模式下滚动元素是 html,应该用 document.documentElement 或直接 window.scrollTo({top: 0}) |
| Firefox 不支持自定义滚动条 | 现在 scrollbar-width / scrollbar-color 已是通用标准属性(Chrome 121+ / Safari 18.2+) |
| 新旧两套滚动条样式可以叠加使用 | 一旦 scrollbar-width / scrollbar-color 取了非 auto 值,Chromium / Safari 会忽略 ::-webkit-scrollbar |
scrollbar-color 可以只写滑块颜色 | 它的合法值只有 auto 或"两个颜色",只写一个是无效值 |
scrollbar-width: none 会让内容滚不动 | 只是滚动条不可见,滚动依旧可用(但会削弱"此处可滚"的提示) |
| 吸附项的高度超过容器就会卡死 | 规范要求"吸附区域盖满视口时任意位置都是有效吸附位置",所以内部仍可滚;真正会出事的是吸附点过于稀疏,会造成部分内容不可达 |
| 不写严格度也能强制吸附 | proximity 是默认值,要强制吸附必须显式写 mandatory |
scroll-snap-type 写在元素上就生效 | 容器必须先是个滚动容器(有 overflow + 溢出内容),否则没有任何吸附 |
scroll-padding 相当于给容器加 padding | 它不改内容布局,只调整滚动定位的参照框;写长属性时要用 scroll-padding-top 这类名字 |
scrollbar-gutter: stable 只有 Firefox 支持 | 现在 Chrome 94+ / Firefox 97+ / Safari 18.2+ 都支持;但对 macOS 的覆盖式滚动条无效 |
用 overflow-y: scroll 消除抖动 | 那会让滚动条常驻(没内容时也占一条)。应使用 scrollbar-gutter: stable |
overscroll-behavior: contain 和 none 一样 | contain 只挡滚动链;none 还会同时禁掉回弹、下拉刷新等本地反馈 |
overscroll-behavior 写哪儿都生效 | 只对滚动容器有效;早期实现还要求它确有可滚动内容 |
scroll-snap-stop: always 在哪些浏览器都生效 | Chrome 75+ / Firefox 103+ / Safari 15+ 才支持,老 Safari 会忽略它 |
下章预告
下一章我们将学习逻辑属性与书写模式,支持全球化的 CSS!