说说 position: sticky
Category(分类): CSS Status: 未知
position: sticky 通常被称为粘性定位,常用于吸顶标题、侧边栏导航、表格表头和分组列表标题。
它保留普通流中的布局空间,在滚动过程中根据设定的阈值调整元素的位置。当滚动位置达到阈值时,元素会在允许的范围内保持相对固定;当它到达所属包含块的边界时,则不能继续粘滞。
sticky不是简单地在relative和fixed之间切换。元素的计算值仍然是sticky,浏览器会结合滚动容器、粘滞阈值和包含块边界计算它的最终位置。
参考文章
一、基本用法
最简单的吸顶效果是:
<header class="site-header">
页面标题
</header>
<main>
<p>页面内容……</p>
<p>页面内容……</p>
<p>页面内容……</p>
</main>
.site-header {
position: sticky;
top: 0;
z-index: 10;
background: #fff;
border-bottom: 1px solid #e5e7eb;
}
当页面向下滚动并且标题到达视口顶部时,标题会在允许的范围内保持在 top: 0 的位置。它仍然会占据原来的布局空间,因此不会像 fixed 那样直接从普通流中移除。
使用逻辑属性
如果需要适配不同书写模式,可以使用逻辑方向属性:
.site-header {
position: sticky;
inset-block-start: 0;
}
常见对应关系:
top对应inset-block-start;bottom对应inset-block-end;left或right对应行方向的逻辑 inset。
二、sticky 的工作方式
1. 初始状态保留普通流空间
sticky 元素在没有达到滚动阈值前,表现得类似普通流中的元素。它不会像 absolute 或 fixed 一样立即脱离普通流。
.title {
position: sticky;
top: 0;
}
此时标题原来的空间仍然存在,后面的内容不会因为标题“消失”而向上移动。
2. 达到阈值后保持粘滞
如果设置了 top: 0,当元素的顶部到达滚动容器顶部时,浏览器会尝试让它保持在该位置:
.title {
position: sticky;
top: 0;
}
如果设置了 top: 1rem,元素会与滚动容器顶部保持 1rem 的距离:
.title {
position: sticky;
top: 1rem;
}
3. 受到包含块边界限制
粘性元素不能无限脱离自己的父级区域。当它到达所属包含块的边界时,会停止继续移动。也就是说,sticky 元素通常只会在自己的父级内容范围内粘滞。
下面的示例中,每个章节标题只会在对应的 section 区域中吸顶:
<div class="article">
<section class="chapter">
<h2 class="chapter-title">第一章</h2>
<p>第一章内容……</p>
<p>第一章内容……</p>
<p>第一章内容……</p>
</section>
<section class="chapter">
<h2 class="chapter-title">第二章</h2>
<p>第二章内容……</p>
<p>第二章内容……</p>
<p>第二章内容……</p>
</section>
</div>
.chapter {
min-height: 600px;
}
.chapter-title {
position: sticky;
top: 0;
z-index: 1;
margin: 0;
padding: 0.75rem 1rem;
background: #fff;
border-bottom: 1px solid #e5e7eb;
}
当第二章标题进入顶部时,第一章标题会随着第一章的包含区域离开而停止粘滞。它不是简单地变成了 fixed,而是受到了章节边界的约束。
三、滚动容器与 overflow
sticky 会参考最近的滚动容器。很多“sticky 不生效”的问题,实际上是因为某个祖先设置了 overflow,改变了滚动参考系。
例如:
.sidebar-wrapper {
height: 100vh;
overflow: auto;
}
.sidebar {
position: sticky;
top: 1rem;
}
此时 .sidebar 会在 .sidebar-wrapper 这个滚动容器中粘滞,而不是直接参考整个页面视口。
overflow: hidden 的影响
祖先元素设置 overflow: hidden、auto 或 scroll 后,可能建立滚动机制或改变 sticky 的粘滞边界。overflow: hidden 不一定让 sticky 彻底失效,但如果该祖先没有实际的滚动空间,效果可能看起来像没有生效。
排查时应检查:
- 哪个元素真正发生滚动;
- sticky 元素最近的滚动祖先是谁;
- 祖先是否设置了
overflow-x或overflow-y; - 滚动容器是否有明确高度;
- 父级是否有足够内容让元素产生粘滞过程。
常见问题代码:
.wrapper {
overflow: hidden;
}
.sticky {
position: sticky;
top: 0;
}
如果 .wrapper 没有足够高度或滚动内容,.sticky 可能没有明显的滚动空间。可以根据需求移除不必要的 overflow,或者明确设置滚动容器:
.wrapper {
max-height: 100vh;
overflow-y: auto;
}
overflow: clip与overflow: hidden的滚动容器行为并不完全相同。涉及复杂裁剪和粘性定位时,应在目标浏览器中实际测试。
四、必须设置粘滞阈值
垂直方向的 sticky 通常需要设置 top 或 bottom,水平方向则需要设置 left 或 right:
.vertical-sticky {
position: sticky;
top: 0;
}
.horizontal-sticky {
position: sticky;
left: 0;
}
如果没有设置对应滚动方向的 inset,元素通常不会表现出明显的粘滞效果:
/* 缺少阈值,通常看不到粘滞效果 */
.sticky {
position: sticky;
}
同时设置多个方向时,最终结果还会受到元素尺寸、书写模式和粘滞视图范围影响。常规吸顶场景只设置 top,通常最容易理解和维护。
五、常见应用场景
1. 吸顶导航
.navbar {
position: sticky;
top: 0;
z-index: 100;
min-height: 3rem;
background: #fff;
box-shadow: 0 1px 4px rgb(0 0 0 / 12%);
}
2. 侧边栏目录
<div class="layout">
<main class="content">
<p>正文内容……</p>
<p>正文内容……</p>
<p>正文内容……</p>
</main>
<aside class="toc">
<nav aria-label="文章目录">
<a href="#intro">简介</a>
<a href="#usage">用法</a>
<a href="#notes">注意事项</a>
</nav>
</aside>
</div>
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 240px;
gap: 2rem;
align-items: start;
}
.toc {
position: sticky;
top: 1rem;
max-height: calc(100vh - 2rem);
overflow: auto;
}
侧边栏需要注意:
- Grid 或 Flex 容器不要把侧边栏强制拉伸到过高;
- 使用
align-self: start可以避免某些拉伸场景; - 目录内容过多时,应该给目录设置最大高度和内部滚动;
- 如果页面有固定头部,
top应预留头部高度。
3. 表格表头
.table-wrapper {
overflow: auto;
}
thead th {
position: sticky;
top: 0;
z-index: 1;
background: #f8fafc;
}
表格场景还要注意:
- 外层横向滚动容器会影响 sticky 的参考系;
- 表头背景不能透明,否则下面的内容会穿透;
- 不同浏览器对表格内部元素的粘性定位支持细节可能不同;
- 如果同时固定首列,需要分别设置
left、top和合适的z-index。
4. 横向滚动列表
.tabs {
display: flex;
gap: 1rem;
overflow-x: auto;
}
.tabs__active {
position: sticky;
left: 0;
flex: 0 0 auto;
background: #fff;
}
横向 sticky 需要横向滚动空间,并且应结合书写方向测试 left、right 或逻辑 inset 的效果。
六、Flex 和 Grid 中的注意事项
Flex 拉伸导致没有滚动空间
在 Flex 容器中,默认的 align-items: stretch 可能使子项被拉伸到与容器一样高,从而没有明显的粘滞空间:
.layout {
display: flex;
align-items: flex-start;
}
.sidebar {
position: sticky;
top: 1rem;
align-self: flex-start;
}
Grid 项目设置起始对齐
.layout {
display: grid;
grid-template-columns: 1fr 240px;
align-items: start;
}
.sidebar {
position: sticky;
top: 1rem;
}
这不是所有布局中必需的配置,但当 sticky 看起来不生效时,align-items、align-self、父级高度和滚动容器是值得优先检查的项目。
七、sticky 与其他定位方式的区别
| 定位方式 | 是否保留普通流空间 | 是否跟随滚动 | 常见参考系 | 常见用途 |
|---|---|---|---|---|
static | 是 | 是 | 普通流 | 默认布局 |
relative | 是 | 是 | 元素自身原始位置 | 建立包含块、轻微偏移 |
absolute | 否 | 通常随包含块滚动 | 定位包含块 | 角标、浮层、覆盖元素 |
fixed | 否 | 通常不随页面滚动 | 通常是视口 | 固定导航、浮动按钮、遮罩 |
sticky | 是 | 在阈值范围内粘滞 | 最近滚动容器和包含块 | 吸顶标题、侧边栏、表头 |
sticky 与 fixed 的主要区别是:
fixed脱离普通流,通常相对于视口定位;sticky保留普通流空间,并受到滚动容器和父级边界限制;fixed可能遮挡页面内容,需要额外给正文预留空间;sticky通常只在所属容器的范围内保持粘滞。
八、为什么 sticky 不生效?
可以按照以下顺序排查:
1. 是否设置了阈值
.sticky {
position: sticky;
top: 0;
}
2. 是否真的发生了滚动
如果页面没有足够内容,或者滚动发生在另一个祖先元素中,sticky 可能没有机会触发。
3. 是否被祖先的 overflow 改变了滚动容器
重点检查祖先的:
overflow
overflow-x
overflow-y
4. 父级是否有足够高度
父级高度过小,sticky 没有可粘滞的范围。
5. Flex/Grid 是否产生了拉伸
尝试添加:
align-items: start;
align-self: start;
6. 是否被其他元素遮挡
为 sticky 元素设置背景和适当的 z-index:
.sticky {
z-index: 10;
background: #fff;
}
7. 是否有布局或浏览器实现差异
旧版 Safari 曾经需要使用:
position: -webkit-sticky;
position: sticky;
现代项目通常可以优先使用标准写法,但如果项目需要支持旧版浏览器,应根据实际目标环境测试,而不是无条件添加前缀。
九、可访问性与交互注意事项
粘性元素可能遮挡锚点跳转后的内容。例如页面有固定或粘性头部时,标题可能被头部覆盖,可以使用:
[id] {
scroll-margin-top: 4rem;
}
如果粘性头部高度会变化,可以使用 CSS 自定义属性统一维护:
:root {
--header-height: 4rem;
}
.header {
position: sticky;
top: 0;
min-height: var(--header-height);
}
[id] {
scroll-margin-top: var(--header-height);
}
还应注意:
- sticky 元素不能遮挡键盘焦点;
- 颜色和背景应保证文字对比度;
- 表格和目录固定后仍应保持合理的阅读顺序;
- 不要仅依靠视觉位置变化传递重要状态;
- 在移动端测试地址栏展开、收起和横竖屏切换的效果。
十、总结
position: sticky是一种带滚动约束的粘性定位方式;- 它保留普通流空间,不是简单地切换为
fixed; - 垂直吸顶通常需要设置
top,水平粘滞通常需要设置left或对应逻辑 inset; - sticky 会受到最近滚动容器、祖先
overflow、父级高度和包含块边界影响; overflow: hidden不一定让 sticky 直接失效,但可能改变滚动参考系并让效果不明显;- Flex/Grid 中需要重点检查
align-items、align-self和可用滚动空间; - 粘性元素通常需要背景色和合适的
z-index; - 表头、侧边栏、导航栏和分组标题是常见应用场景;
- 遇到 sticky 不生效时,应先检查阈值、滚动容器、父级高度和祖先 overflow;
- 使用 sticky 时还要考虑锚点滚动、键盘焦点和移动端视口变化。