滚动类问题常同时涉及四个对象:谁裁切内容、谁是真正的滚动容器、滚动到哪里算“可见”、输入事件由浏览器还是应用处理。先找对对象,再设置属性。
overflow: hidden 与 overflow: clip 不等价
| 值 | 裁切 | 是滚动容器 | 可通过脚本滚动 |
|---|---|---|---|
hidden |
是 | 是 | 是 |
clip |
是 | 否 | 否 |
hidden 隐藏滚动 UI,但 scrollTop、scrollTo()、焦点滚入等机制仍能改变它的滚动位置。clip 禁止所有滚动机制,也不会单独建立新的格式化上下文;需要格式化上下文时可组合 display: flow-root。
.pure-clip {
overflow: clip;
overflow-clip-margin: 0.5rem;
}
overflow-clip-margin 可以把裁切边界向外扩一小段,适合保留阴影或发光效果,但它不会把元素变成滚动容器。
来源:CSS Overflow Level 3 的 overflow与 overflow-clip-margin。
scroll-padding 设在容器,scroll-margin 设在目标
固定头部遮住锚点时不要给每次滚动写 JS 偏移:
html {
scroll-padding-block-start: 4rem;
}
.section-heading {
scroll-margin-block-start: 1rem;
}
scroll-padding缩小滚动容器的最佳可视区域,表达“这个容器边缘需要保留多少空间”。scroll-margin扩大目标元素用于滚入视图和 Scroll Snap 的区域,表达“这个目标希望离边缘多远”。
它们影响滚动定位,不会像普通 padding、margin 那样改变正常布局尺寸。
来源:CSS Scroll Snap Level 1 的 scroll snap area、snapport、scroll-margin 与 scroll-padding。
scrollbar-gutter: stable 减少滚动条出现时的横向跳动
.panel {
overflow: auto;
scrollbar-gutter: stable;
}
在使用经典滚动条的平台上,stable 会在需要出现滚动条前预留沟槽,从而避免内容宽度突然变化。both-edges 可以在行内轴两侧保留对称沟槽。覆盖式滚动条是否占据沟槽由规范规则和平台行为决定,因此不要把它当作固定宽度的 padding。
来源:CSS Overflow Level 3 的 scrollbar-gutter。
overscroll-behavior 控制滚动链和边界反馈
内层弹窗滚到底后继续滑动,默认可能带动外层页面。把属性设置在真正滚动的容器上:
.dialog__body {
overflow: auto;
overscroll-behavior: contain;
}
auto:允许默认滚动链和越界反馈。contain:阻止滚动链传给祖先,但保留当前容器自己的越界反馈。none:同时抑制滚动链和越界反馈。
非滚动容器会接受但忽略这个属性。它处理的是滚动边界默认行为,不等于背景内容不可聚焦;模态框仍需要正确的语义、焦点管理和关闭机制。
来源:CSS Overscroll Behavior Level 1。
scroll-behavior: smooth 不控制手指和滚轮本身
这个属性影响锚点导航、滚动 API 和非用户直接发起的滚动定位。用户手指拖动或滚轮产生的滚动不由它强制变平滑,浏览器也可以根据平台约定忽略平滑滚动。
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}
把动效放在 prefers-reduced-motion 条件内,可以尊重希望减少动画的用户。来源:CSS Overflow Level 3 的 scroll-behavior、Media Queries Level 5 的 prefers-reduced-motion。
touch-action 要在手势开始前声明浏览器能做什么
Pointer Events 中,浏览器会在手势开始时根据命中元素及其祖先的 touch-action 决定哪些平移和缩放行为可以接管:
.horizontal-slider {
touch-action: pan-y;
}
这表示浏览器仍可处理纵向页面滚动,而组件自己处理横向拖动。常见值还有 auto、none、pan-x、pan-y、pinch-zoom 和 manipulation。
不要为了方便手势代码就给整个页面设置 touch-action: none;它会禁止相应区域由浏览器执行平移和缩放,可能严重影响可访问性。事件监听器调用 preventDefault() 也不能替代手势开始前的声明。
来源:Pointer Events Level 3 的 touch-action。
pointer-events: none 只退出命中测试
.decorative-overlay {
pointer-events: none;
}
它让该元素自身不成为指针命中目标,适合纯装饰覆盖层。需要记住:
- 后代可以重新设置
pointer-events成为命中目标,事件传播路径仍可能经过父元素。 - 它不等于
disabled,不会自动改变表单语义、焦点顺序、键盘激活或可访问性状态。 - 真正不可交互的控件应优先使用原生
disabled、inert或符合组件语义的状态管理。
来源:CSS Basic User Interface Level 4 的 pointer-events。
user-select 是交互便利,不是复制保护
.drag-handle {
user-select: none;
}
.copy-token {
user-select: all;
}
none适合拖拽手柄、按钮装饰等容易误选文字的局部区域。all让选择动作以整个元素为单位,适合短 token 或命令,但仍应验证移动端体验。- 不要在页面根节点大范围禁止选择;用户可能需要复制、查词、翻译或搜索错误信息。
浏览器和用户工具仍可以提供其他选择方式,所以 user-select: none 从来不是内容保护机制。来源:CSS Basic User Interface Level 4 的 user-select。
accent-color 保留原生控件能力,只改变强调色
:root {
accent-color: #6d5dfc;
}
它为 checkbox、radio、range、progress 等由浏览器绘制的控件提供强调色,同时保留原生交互、状态和平台适配。它不是完整主题系统;具体哪些部位使用该颜色由用户代理决定,用户的对比度偏好也可以覆盖作者选择。