poistion: sticky 失效的所有原因

1,491 阅读23分钟

在 Web 开发和设计的世界里,CSS 在创建视觉吸引力和互动性强的网站或应用方面起着至关重要的作用。当涉及到在 Web 页面上定位元素时,Web 开发者通常要让某些元素在滚动时固定在特定的位置。传统上,position: fixed 属性被用于这个目的。然而,还有一个更好的替代方案,它提供了更多的灵活性和更好的用户体验 —— position: sticky

可以说,CSS 的 position: sticky 是 Web 开发者梦寐以求的功能。它允许元素根据滚动位置在相对定位(position: relative)和固定定位(position:fixed)之间切换,而不需要任何 JavaScript 。这对于持久化的页眉、侧边栏或任何你想在滚动时保持可见的元素非常有用。

然而,Web 开发者在使用 position: sticky 时,时常会感到困惑:你认为某个元素应该是粘性的,但它却没有起作用。这个现象在 Stack Overflow 上同样被很多 Web 开发者提到:

换句话说,对于众多 Web 开发者而言,使用 position: sticky 还是很棘手的,因为它有许多限制。如果你对其不太了解的话,那么在使用它时,时常会不生效。今天,我将在接下来的内容中,和大家一起探讨一下 position: sticky 可能失效的情况以及如何解决这些问题!

CSS 中的定位:position

CSS 的 position 属性主要用于指定元素的定位类型,它们决定了元素之间的相对位置:

  • static :默认值,元素按照文档的正常流进行定位

  • relative :元素相对于其正常位置进行定位,即相对于自身进行定位

  • absolute :元素相对于偏移父元素(最近的非 static 定位的祖先元素)进行定位

  • fixed :元素相对于视口进行定位

  • sticky :它是 position: relativeposition: fixed 的混合体。它在达到指定的滚动位置之前,表现得像相对定位的元素,一旦达到指定的位置,就变为固定定位

Demo 地址:codepen.io/airen/full/…

position: sticky 工作原理

position: sticky 工作原理比较简单:

  • 1️⃣:你的元素相对于其正常位置进行相对定位(这意味着它处于文档的流中),并像其他元素一样滚动

  • 2️⃣:当它达到特定的滚动位置(由 toprightbottomright 定义)时,变为固定定位

  • 3️⃣:它保持固定定位状态,直到触碰到其像元素的底部(父元素不会粘住,仍然会向上滚动),此时它返回到其正常位置

换句话说,如果你要创建一个粘性元素,那么需要遵循以下步骤:

  • 1️⃣:确定你想要设置为粘性的元素

  • 2️⃣:在元素上设置 CSS 属性 position: sticky;

  • 3️⃣:使用 topbottomleftright 属性指定偏移(inset)阈值

以下是一个使导航菜单变为粘性的 CSS 代码示例:

.submenu {
    position: sticky; 
    top: 0; 
}

浏览器还未滚动时,粘性定位元素(.submenu)距离浏览器视窗顶部边缘大于 0px ,它表现得像相对定位;当浏览器向下滚动,并且滚动到某个阈值(比如,粘性定位元素距离浏览器视窗顶部边缘距离是 0)时,它表现得像固定定位。

正如你所看到的那样。当元素的 position 属性的值显式设置为 sticky 时,该元素被称为“粘性元素”,即粘性定位。它的渲染效果同时具有相对定位(position: relative)和固定定位(position:fixed)效果。也就是说,粘性定位(sticky)是相对定位(relative)和固定定位(fixed)的混合体,它允许被定位的元素表现得像相对定位一样,直到它滚动到某个阈值点为值,此后它表现得就像固定定位一样。

为什么选择 position: sticky

使用粘性元素的好处包括:

  • 吸引用户注意:粘性元素通过引人注目的视觉效果和位置来捕捉用户的注意力,传达品牌信息或行动号召。例如,使用一个醒目、视觉吸引力强的横幅来推广你的服务或产品

  • 定制互动:你可以定制粘性元素,使其出现在特定页面或显示与用户兴趣相关的内容,以传递有针对性的消息

  • 改善用户旅程:粘性元素可以通过提供快速访问客户支持、社交链接、“联系我们”按钮或常见问题解答(FAQ)部分来增强用户体验

  • 增加品牌意识:像品牌横幅这样的粘性元素可以在用户滚动时持续提醒他们你的业务。与典型广告不同,粘性元素保持固定位置,有助于加强品牌在用户心中的认知

除此之外,使用 position: sticky ,你可以创建在滚动过程中表现得像相对定位的元素(position: relative),直到达到指定的偏移(inset)阈值。在那时,元素会变得“粘性”,并使其位置保持固定(表现的像固定定位 position:fixed),直到用户滚动超过“粘性元素”。

这也是使用 position: sticky 最大的好处,即能够结合相对定位和固定定位两者的优点:相对定位的灵活性和需要时的固定定位行为。这使得它成为理想的选择,适用于需要在滚动内容保持可见的元素,如页眉、导航菜单、侧边栏以及其他元素。

而且,使用 position: sticky 相对于 position: fixed 的另一个优势是可以提升用户体验。当元素设置为 position: fixed 时,它在视口中保持固定,而不管周围的内容。这通常会导致重叠和遮挡页面上的其他得要元素的视图。反之,position: sticky 允许元素与其他内容自然流动,直到达到某个滚动点(偏移阈值)。这避免了元素覆盖其他内容,并确保用户获得无缝的浏览体验。稍后会向大家阐述 position: stickyposition: fixed 两者之间的一些利弊!

粘性定位的常见用途和位置

将你的粘性元素放置在最佳位置,以吸引用户的注意并在理想时机促使他们转化。这个位置可以是在屏幕的一侧、顶部,或者作为弹出模块,具体取决于什么对用户最有益。以下是一些常见的粘性元素用途和位置,这些元素有可能带来良好的转化效果:

导航菜单

粘性导航菜单是一种常见且有效的设计选择。当你浏览网站时,粘性菜单始终保持在页面顶部,使你在滚动时可以轻松找到重要链接。这帮助用户在网站上进行导航,而无需上下滚动,从而改善用户体验。

社交分享图标

社交分享图标通常保持在屏幕的一侧或底部。这些粘性元素在博客和文章中很常见,允许用户轻松分享内容。

CTA 按钮

粘性CTA按钮(例如“注册”或“立即购买”)通常放置在屏幕底部,帮助用户采取行动。

公告栏

通过公告和促销栏,你可以利用网站顶部区域告知读者突发新闻、活动、促销活动或其他你希望突出展示的内容。

粘性侧边广告

粘性侧边广告提高了可见性和收入,同时为用户提供了快速访问有用内容的方式。这些广告在整个滚动后进行轮换,以最大化空间和收入潜力,并确保用户能够看到它们。

回到顶部按钮

用户经常滚动浏览长页面,回到顶部按钮提供了一种方便的方式,可以迅速返回页面顶部。这在内容较多的页面上特别有用。

Cookie 通知

粘性 Cookie 通知允许用户高效地设置他们的 Cookie 偏好和管理隐私设置。

粘性 TOC

“TOC” 通常是 “Table of Contents” 的缩写,它是一种导航工具,列出了文档或网页中主要章节或部分的标题,方便用户快速找到他们感兴趣的内容。在 Web 上,目录通常是一个列表或链接集合,点击后可以跳转到对应的页面部分。设置一个粘性 TOC,给用户提供了快速访问有用内容的方式:

在 Web 上,类似这些用例非常多,这里就不一一列举了!

粘性定位如何真正起作用的?

为了让 position: sticky 生效,浏览器需要知道何时在相对定位和固定定位之间切换。这个特定的滚动位置由 inset 值(toprightbottomleft)来指定。

inset 值是从最近的滚动区域测量的,通常是视口。滚动区域是一个 overflow 设置 scrollautohidden 并具有显式高度的元素。视口是浏览器中页面的可见部分,它默认就是一个滚动区域。

粘性元素需要比滚动区域和其父元素(如果在它和滚动区域之间有父元素)短。如果粘性元素比滚动区域高,那么在达到 inset 值时它将永远不会完全可见,因此无法粘住;如果它与父元素的高度相同或更高,那么父元素会继续滚动,粘性元素也会随之滚动。

也就是说,需要满足以下条件,粘性定位才能真正起作用:

  • 一个具有 position: stickyinset 值(toprightbottomleft)的元素

  • 一个包含粘性元素的滚动区域(粘附容器)

  • 粘性元素需要比其父元素(如果有的话)短

  • 粘性元素需要比滚动区域短

简单的解释一下。

每当一个元素是一个粘性定位元素时,粘性定位元素的容器是唯一可以粘附的区域。也就是说,粘性定位主要有两个部分组成:

  • 粘附项目:就是粘性定位元素,即设置了 position: sticky 的元素。当浏览器视窗位置与定义的位置相匹配时,比如 top: 0 ,此时,粘附项目就会脱离文档流,会被放置在一个单独的层中

  • 粘附容器:即粘性定位元素的容器。这是粘附项目脱离文档流之后可浮动的最大范围区域

当你定义了带有 position: sticky 的元素时,将自动定义其父元素为粘附容器!这一点需要记住,非常重要!粘附容器是粘附项目的范围,粘附项目不能脱离其对应粘附容器的范围。

言外之意,上面所列的几个要求当中的每一个都可能会使用粘性定位不起作用的来源,接下来我们会更详细地讨论它们。

滚动区域的问题

position: sticky 失效的最常见原因之一与其包含元素有关,即你的元素所在的滚动区域或视口。

overflow: hidden 会创建一个新的滚动区域:一个没有滚动条但仍然可以通过 JavaScript 方式滚动的区域。

这意味着,如果在粘性元素的某个祖先元素上设置了 overflow 属性的值为 hidden (或 auto ,或 scroll ,用于更明确的滚动),那么粘性元素现在会将 inset 值(例如 toprightbottomleft)与该祖先元素进行比较,而不是与视口进行比较。因此,在你期望粘性元素固定时,它们可能不会如你预期那样被固定住。具体解释如下:

  • 祖先元素上的 overflow:当 sticky 元素的某个祖先元素设置了 overflow: hiddenoverflow: autooverflow: scroll 时,这个祖先元素会形成一个新的包含块(Containing Block)

  • 相对于包含块sticky 元素会相对于这个新的包含块计算其粘性位置(例如 top: 0 表示粘在包含块的顶部),而不是相对于整个视口(Viewport)

  • 粘性行为受影响:由于粘性位置现在是相对于包含块而不是视口,因此在滚动页面时,sticky 元素可能不会在你预期的时刻固定住。这是因为它的参考点已经改变了

请注意,当你在某个地方设置 overflow-x:hidden 时,它也会在同一个元素上强制 overflow-y: auto ,所以你仍然会创建一个新的滚动区域。

例如:

<div class="container">
  <section class="sticky">01</section>
  <section class="sticky">02</section>
  <section class="sticky">03</section>
  <section class="sticky">04</section>
  <section class="sticky">05</section>
</div>
.container {
    overflow-x: hidden;
    
    /* 相当于 */
    overflow-x: hidden;
    overflow-y: auto;
}

.sticky {
    position: sticky;
    top: 0;
}

Demo 地址:codepen.io/airen/full/…

正如你所看到的,sticky 元素并没有在你期望的地方粘住。

为了确保你没有在粘性元素和视口之间创建新的滚动区域(除非你明确需要这样做)。如果你确实需要 overflow:hidden ,你可以将其换成 overflow: clip ,它会隐藏溢出但也会禁用 JavaScript 滚动。

.container {
    overflow: visible; /* 或 clip */
}

Demo 地址:codepen.io/airen/full/…

有关于 overflow 更详细的介绍,请移步阅读《溢出常见问题与排查》!

在 CSS 中,一个元素只有具有指定高度时才能滚动。因此,当它没有设置高度时,浏览器无法确定粘性元素何时应该粘住。

.container {
    height: 100vh;
    overflow-x: hidden;
    section {
        position: sticky;
        top: 0;
    }
}

Demo 地址:codepen.io/airen/full/…

这也适用于粘性元素和滚动区域之间的任何元素。

你的容器应该比粘性元素高。如果没有明确的高度,它将与其内部的内容一样高。有时,当容器内部不仅有粘性元素时,这是可行的,因为容器内的其他元素会使其足够高,让粘性元素粘住。

粘性元素的问题

粘性元素本身也可能是定位失败的原因。

例如,粘性元素缺少 inset 值。换句话说,粘性元素需要设置至少一个 toprightbottomleft 值,以便浏览器知道应该在哪里粘住。

/* 错误示例, position: sticky 不生效 */
.sticky-element {
    position: sticky;
    /* 缺少 top、right、bottom 或 left 值 */
}

/* 修正 */
.sticky-element {
  position: sticky;
  top: 0; /* 添加至少一个 top、right、bottom 或 left */
}

另一个使粘性定位失效的原因是粘性元素大于其容器,它将不会粘住,因为粘性元素需要在完全可见之前才能粘住:

/* 错误示例,粘性元素高度大于其容器 */
.sticky-element {
    position: sticky;
    top: 0;
    height: 110%; /* 高于容器 */
}

你不能总是控制元素的高度,所以如果你不能改变元素的高度,你可以限制粘性元素的高度:

.sticky-element {
    position: sticky;
    top: 0;
    max-height: 50%;  /* 将高度限制为容器的高度 */
    overflow-y: auto; /* 如果需要,允许在元素内滚动 */
}

如果你将它设置得与容器一样高,那么它将永远不会粘住,所以你需要确保它比容器短才能保持在视图中。

Flexbox 和 Grid 布局的粘性定位

在使用粘性定位时,Flexbox 和 Grid 布局可能会引入额外的复杂性,因为这些布局系统也会影响内部元素的属性。

首先来看 Flexbox 布局中的粘性定位。

在一个 Flexbox 容器中,粘性元素可能不会像预期的那样粘住,因为弹性布局默认不会创建滚动区域(其高度决于其子元素)。你需要在 Flexbox 容器上设置一个高度,并允许滚动以使粘性元素生效。

<div class="flex-container">
    <div class="sticky-element">I should be sticky</div>
    <div class="flex-item">Flex content</div>
</div>
.flex-container {
    display: flex;
    gap: 1rem;
    overflow-y: auto;
    height: 100vh;
}

.sticky-element {
    position: sticky;
    top: 0;
}

Demo 地址:codepen.io/airen/full/…

这种现象同样会出现在 Grid 布局中,但遗憾的是,即使在网格容器上显示式设置高度,粘性定位还是会失效:

<div class="grid-container">
    <div class="sticky-element">I should be sticky</div>
    <div class="grid-item">Grid content</div>
</div>
.grid-container {
    display: grid;
    gap: 1rem;
    overflow-y: auto;
    height: 100vh;
    grid-template-columns: auto minmax(0, 1fr);
}

.sticky-element {
    position: sticky;
    top: 0;
}

Demo 地址:codepen.io/airen/full/…

你可能会想到在网格容器上使用 grid-template-rows 来指定网格行轨道的高度,使粘性定位生效:

.grid-container {
    display: grid;
    gap: 1rem;
    overflow-y: auto;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-rows: 100vh;
}

Demo 地址:codepen.io/airen/full/…

正如你所看到的,这样做的确使粘性定位生效了,并且符合你的预期。但它有一定的局限性,当你的网格项目增加时,它将无法正常的工作:

另外,我们在构建 Web 布局时,很多时候并不能给 Flexbox 或 Grid 容器设置固定的高度,因为这样做很可能会打破 Web 布局,或者无法构建出符合预期的 Web 布局效果。

在这种情况之下,如果你将其中一个 Flex 项目或网格项目设置为粘性元素,它并无法使粘性元素正常工作。我们以网格布局为例:

<div class="wrapper">
    <aside></aside>
    <main></main>
</div>
.wrapper {
    display: grid;
    grid-template-columns: 250px minmax(10px, 1fr);
    grid-gap: 1rem;
}

aside {
    position: sticky;
    top: 0;
}

上面示例中的 aside 是一个粘性元素,但它无法正常工作。注意,将上面的网格布局替换为 Flexbox 布局,问题同样存在。

这是因为,Flexbox 和 Grid 容器的 align-items 默认值为 stretch 。这意味着粘性元素 aside 与包含内容的网格单元 main 一样高:

这意味着,每当它到达 inset 值并应该粘住时,它会立即解除粘性,因为父元素会继续滚动。你可以通过在网格容器上设置 align-itemsstart 来修复此问题:

.wrapper {
    display: grid;
    grid-template-columns: 250px minmax(10px, 1fr);
    grid-gap: 1rem;
    align-items: start;
}

如果你希望其他网格项目保持拉伸状态,你也可以在粘性元素上设置 align-self: start

aside {
    position: sticky;
    top: 0;
    align-self: start;
}

Demo 地址:codepen.io/airen/full/…

注意,如果你使用的是 Flexbox 布局,align-itemsalign-self 的值通常为 flex-start 。除此之外,你还可以在粘性元素上设置 margin-bottom: auto 达到类似的效果。有关于这方面更详细的介绍,请参阅《Flexbox 布局中的对齐方式》和《Grid 布局中的对齐方式》!

简单的小结一下:

position: sticky 是一个无需 JavaScript 即可创建粘性元素的强大工具,但它有一些不易察觉的限制。为了确保你的粘性元素按预期工作,你需要确保:

  • 粘性元素在滚动区域中(并且在你预期的滚动区域中)

  • 粘性元素有一个 inset 值(即 toprightbottomleft

  • 粘性元素比滚动区域短

通过理解这些限制,你可以确保你的粘性元素按预期工作!

使用粘性定位常会见的一些问题?

现在,你了解了粘性定位的工作原理以及如何才能使其正常工作。但在实际使用的过程中还是会碰到一些常见的问题,例如:

何时选择 position: fixed 更佳

虽然 position: sticky 相比于 position: fixed 有许多优势,但在某些情况下,使用 position: fixed 更为合适。让我们来探讨一下这些场景。

持久元素定位

在某些情况下,你可能希望一个元素在屏幕上的特定位置保持固定,无论滚动或其他交互情况如何。这时,position: fixed 显得尤为有效。像“返回顶部”按钮或持久侧边栏这样的元素常常从 position: fixed 中受益,因为它们始终可见且易于访问。

.back-to-top {
    position: fixed;
    bottom: 20px;
    right: 20px;
}

在这个示例中,.back-to-top 按钮将固定在视口的右下角,使用户能够轻松地返回页面顶部,无论滚动位置如何。

重叠元素

如果你的 Web 上有重叠的元素,并且你希望一个元素覆盖其他元素,position: fixed 是一个合适的选择。与 position: sticky 不同,position: fixed 会完全将元素从文档流中移除,并保持相对于视口的固定。这在创建覆盖层、模态窗口或工具提示时非常有用。

.overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 9999;
}

.tooltip {
    position: fixed;
    top: 50px;
    left: 50px;
}

在这个示例中,.overlay 元素以半透明背景覆盖整个视口,而 .tooltip 元素保持在特定位置,并且显示在其他元素之上。

可滚动容器

虽然 position: sticky 在主要视口中表现良好,但在可滚动容器内可能无法提供所需的行为。如果你有一个具有固定高度和可滚动内容的容器,使用 position: fixed 使容器内的特定元素保持固定,可以确保这些元素的可见性和固定定位,即使容器内容被滚动。

<div class="scrollable-container">
    <div class="sticky-element">
        <!-- 内容在这里 -->
    </div>
</div>
.scrollable-container {
    position: relative;
    height: 400px;
    overflow-y: scroll;
}

.sticky-element {
    position: fixed;
    top: 20px;
}

在这个示例中,.sticky-element 在可滚动容器内保持固定位置。当容器滚动时,元素保持在指定位置,从而在滚动区域内提供持久的可见性。

浏览器兼容性

虽然 position: sticky 在现代浏览器中得到了广泛支持,但仍然可能有一些旧版浏览器或特定版本不完全支持。在这种情况下,使用 position: fixed 提供一个更可靠的备用选项,以确保在不同浏览器间的一致行为。

使用 position: fixed 时,重要的是为不支持 position: sticky 的浏览器提供备选方案。你可以通过使用媒体查询条件 CSS 来实现:

.sticky-element {
    position: fixed;
    top: 20px;
}

/* 针对不支持 position: sticky 的浏览器的备选方案 */
@supports not (position: sticky) {
    .sticky-element {
        position: static;
    }
}

在这个示例中,如果浏览器不支持 position: sticky.sticky-element 将默认为 position: static,确保它仍然可见,尽管不会有粘性行为。

创建视差效果

视差滚动效果,即元素在滚动时以不同的速度移动,通常使用 position: fixed 实现。通过在背景中固定某些元素,同时让其他元素正常滚动,你可以创建引人入胜的视觉效果。

<div class="parallax-container">
    <div class="background-element"></div>
    <div class="foreground-element">
        <!-- 内容在这里 -->
    </div>
</div>
.parallax-container {
    position: relative;
    height: 600px;
    overflow: hidden;
}

.background-element {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-image: url('background-image.jpg');
    background-size: cover;
    background-position: center;
    z-index: -1;
}

.foreground-element {
    position: relative;
    z-index: 1;
}

在这个示例中,.background-element 使用 position: fixed 保持固定,创建视差效果,而 .foreground-element.parallax-container 内正常滚动。

记住,选择 position: sticky 还是 position: fixed 取决于你的设计需求和期望的用户体验。了解每个属性的优缺点将帮助你做出明智的决策。

在 Web 创建粘性元素时,position: sticky 提供了比传统的 position: fixed 更好的替代方案。它的灵活性、改善的用户体验以及易于实现的特性使其成为网页开发者和设计师的重要工具。

通过使用 position: sticky,你可以提升网站的互动性和可用性,为用户提供无缝的滚动体验。因此,下次你需要将一个元素固定在特定位置时,记得选择 position: sticky 而不是 position: fixed

小结

CSS 的 position 同样是 Web 布局中不可或缺的部分,在一些特殊的布局中是离不开 position 的能力的。该功能模块提供了多种不同的定位方式,比如 relativeabsolutefixedsticky 。尤其是新增的 sitcky 定位,它可以让 Web 开发者不依赖任何 JavaScript 脚本就可以实现吸附效果,比如吸顶效果。

只不过,在使用 position: sticky 时,有时候能正常工作,有时候又无法正常工作,这令很多 Web 开发者在使用 position: sticky 时感到很困惑。在这节课中,我们除了阐述了 CSS position 的基础知识之外,还着重阐述了 sticky 失效的原因以及相应的修复方式。

  • 尽量避开粘性定位元素的祖先元素的 overflow 属性为 autoscrollhiddenoverlay 。如果布局无法避免使用 overflow 的话,也应该尽可能使用 clip 来替代 hidden ;另外,还可以在溢出容器上显式设置一个高度值,避免粘性定位失效。

  • 在使用 position: sticky 时,一定要记得显式设置一个阈值,即 使用 toprightbottomleft 来设置粘性定位的位置。

  • 要确保粘附容器的高度大于粘附项目的高度,尤其是 CSS Flexbox 和 CSS Grid 布局时,记得在 Flex 容器(或 Grid 容器)上重置 align-items 的初始值(比如设置为 flex-startstart);或者在粘附项目上重置 align-self 的值为 flex-start (Flexbox 布局)或 start (Grid 布局);或者在粘附项目上重置 margin 的值为 auto ,比如吸顶时,设置 margin-bottom: auto ,吸底时,设置 margin-top: auto

掌握了上面这几点,你在使用 position: sticky 不再会碰到粘性定位失效的情景,即使碰到了,也可以快速定位到原因和快速修复。如果你想更深入了解 这方面的内容,请移步阅读《position:sticky 失效与修复》!


damo.gif

如果你觉得该教程对你有所帮助,请给我点个赞。要是你喜欢 CSS ,或者想进一步了解和掌握 CSS 相关的知识,请关注我的专栏,或者移步阅读下面这些系列教程: