大约两年看在看 ECMA 2015 的时候,发现在滚动时每个章节的标题都会固定在页面的顶端,好奇的我看了下它的 CSS,发现只有简单的一行 position: sticky。当时只有 Firefox 实现了这个特性,不免心生遗憾,今天恰巧注意到 Chrome 从 56 也支持这个特性了!这里我们就来介绍下这个神奇的特性吧。

先暏为快

先看下面的 JsFiddle,注意滚动时左侧导航栏的行为。

下面这个例子是 MDN 的 demo:

只需要一行 CSS 就能实现,没有这个特性时,我们需要用 JS 监控滚动事件来实现。下面我们看看它的工作原理吧。

工作原理

Sticky positioning can be thought of as a hybrid of relative and fixed positioning. A stickily positioned element is treated as relatively positioned until it crosses a specified threshold, at which point it is treated as fixed.

可以认为 sticky 是 relative 及 fixed 两种 position 的混合。当一个元素的 position 为 sticky 时,它首先会被当成是 position: relative,之后,当它的位置超出一定的阈值时,该元素就被认为是 position: fixed

默认情况下,元素被当作是 position: relative,当用户滚动页面时,元素跟着它的父元素一起滚动。当元素和视区(viewport,这里可以理解成浏览器窗口)的距离小于(通过 top: 10px 等)指定的数值时,元素被认为是 position: fixed。造成的效果是元素和 viewport 的距离保持不变,不会小于指定的距离。

例如:

#one {
position: sticky;
top: 10px;
}

当滚动屏幕使得元素 #one 与视区的距离小于 10px 时,它就变成了 position: fixed; top: 10px,此时继续滚动的话,#one 是不会移动的,因此也称为黏性 (sticky)的。

最后要注意一个注意点, position: sticky 的元素是不会“超出”父元素的。当滚动时,父元素也快离开屏幕时,子元素是不会继续保持 sticky 的状态的,它会随着父元素一起“滚”出屏幕。可以参考上节给出的 MDN 的例子。

因此,如果将 position: stickyflexbox 一起使用,要注意 flexbox 默认会拉伸元素。如第一个例子中,我们使用了 align-items: flex-start; 来保证导航栏的高度不会和父元素一样,否则 sticky 就没作用了。

最后

本文只是对 position: sticky 做一个简单的介绍,对它的介绍也是以自己的直观理解为主,如果遇到问题还请查阅官方文档。