这里说的 Scroll-Then-Fixed 是指在网页中有一些元素开始时可以跟随内容滚动,而待滚动到特定位置之后这些元素的位置保持不动,不会跟着内容滚动到看不见的区域。

下面我们来介绍一下实现方法。

本文翻译整理自: Scroll-Then-Fix Content

1 两个状态

首先,目标元素存在两个状态,分别是跟随滚动状态,和固定位置状态。这个两个状态对应不同的 CSS 类。我们的核心思想是在合适的时机在这两个不同状态之间进行切换。

1.1 跟随滚动状态

以开头的动图中的搜索框为例。

其 CSS 内容如下(这里使用了 SCSS 来简化代码形式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.top-header {
position: fixed;
top: 0;
left: 0;
width: 320px;
height: 60px;
}

.search {
/* Container just in case we want more than just the search input to come along */
position: absolute;
top: 155px;
left: 20px;
right: 20px;
input {
width: 265px;
transition: width 0.2s;
-webkit-appearance: none; /* Autoprefixer doesn't catch this */
}
}

.top {
height: 250px; /* Space in here for search */
padding-top: 40px;
position: relative;
}

1.2 固定状态

假设我们通过在前一个状态的基础上加一个 fix-search 的类来实现滚动固定,则对应的 SCC 代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.top-header {
... .fix-search & {
background: #eee;
}
}

.search {
/* Container just in case we want more than just the search input to come along */
... .fix-search & {
position: fixed;
top: 10px;
input {
width: 250px;
}
}
}

2 状态切换

状态切换可以用 JS 代码来实现。这里给出了利用 jQuery 的一个案例

1
2
3
4
5
6
7
8
9
var wrap = $("#wrap");

wrap.on("scroll", function (e) {
if (this.scrollTop > 147) {
wrap.addClass("fix-search");
} else {
wrap.removeClass("fix-search");
}
});

我实际用的是下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let rc = document.getElementsByClassName("post-toc");
if (rc.length == 0) {
return;
}
window.onscroll = function () {
// 185 是 toc 部分两个状态的 top 位置差
// 64 是导航栏的高度
if (document.scrollingElement.scrollTop > 185 + 64) {
for (xx of rc) {
xx.classList.add("fixed");
}
} else {
for (xx of rc) {
xx.classList.remove("fixed");
}
}
};

上面的代码解决了原版代码的这几个问题:

  1. 依赖 JQuery 的问题;
  2. 部分元素的 onscroll 事件不能正常触发的问题;
  3. 部分元素的 scrollTop 始终返回 0 的问题;

注意根据这个问题的说法:在设置高度属性之后,window.onscroll 的响应可能有问题。