简单的说明一下,使用这个标题并不就是说要使用全英文来写这篇文章,主要是实在想不到更好的叫法了,也不知道怎么样能够更好的翻译成中文。

可以简单地理解为:使用 CSS 来实现一个阅读文章时的简单的进度条效果。

本文所需要用到的背景知识点包括:background-size, linear-gradient, calc() 函数, vh 单位

Indicator 是什么?可以把它看作是一个指标,这种指标其实很常见,在我们阅读文章的时候,我们会将文章向上滚动,一些网站的做法就是给用户一个指标,标示用户已经读了多少内容了,这个指标就类似于一个进度条的样子,鼠标每向上或者向下滚动一点,这“进度条”会增加或者减少一点。用户阅读完毕的时候,它的长度也会达到整个网页的宽度。

一般情况下,如果要实现一个这样的效果,我们首先想到的肯定是使用 JavaScript。通过使用 JavaScript 来根据用户滚动的距离,然后,将这个距离和 Indicator 的总长度以及剩余内容的高度综合进行比较,进行一些换算,然后改变 Indicator 的长度,这样,就实现了一个 Indicator 了。

本文不会讲解如何使用 JavaScript 来实现此效果,如果有需要的,可以参考 Bloomberg Article Scroll Indicator

那么,本文主要讲解的就是如何使用 CSS 来实现此效果。

首先,我们将 HTML 都写好了(由于涉及到滚动,所以,可以随意的多加点内容,此处只是示范性地写了一点内容,具体的读者可以自行添加更多内容):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<header>
<h1>Scroll Indicator</h1>
</header>
<main>
<h2>I was interested to see if I could make a scroll indicator like this with just CSS.</h2>
<p>You can! But maybe you shouldn't. This is an interesting consequence of a bunch of hacks held together with duct tape. It uses z-index hacks, gradient hacks and tricks with calc and viewport units.</p>
<p>Having said that, hacks are not always bad. I love hacks and many of us have made quite a good living selling floats and clearfixes.</p>
<p>The techniques used here are well supported, if not conventional. If you can read the CSS, understand how it works, and how to change it, and you think this works better for you than JavaScript, feel free to implement it. Just be aware of the z-index behaviour and possible conflict with other CSS using negative z-index.</p>
<hr>
<p>Cras mattis consectetur purus sit amet fermentum. Donec id elit non mi porta gravida at eget metus. Donec id elit non mi porta gravida at eget metus. Aenean lacinia bibendum nulla sed consectetur.</p>
<h3>Tristique Aenean Etiam Cras</h3>
<p>Donec id elit non mi porta gravida at eget metus. Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue. Donec sed odio dui. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.</p>
<p>Cras mattis consectetur purus sit amet fermentum. Donec id elit non mi porta gravida at eget metus. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Etiam porta sem malesuada magna mollis euismod. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec ullamcorper nulla non metus auctor fringilla.</p>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Donec ullamcorper nulla non metus auctor fringilla. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Aenean lacinia bibendum nulla sed consectetur. Nulla vitae elit libero, a pharetra augue.</p>
<p>Donec sed odio dui. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Cras mattis consectetur purus sit amet fermentum. Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
</main>

然后,我们简单的调整一下样式:

1
2
3
4
5
6
7
8
9
10
html,
body {
margin: 0;
padding: 0;
}
body {
font-family: "Times New Roman", Courier, monospace;
font-size: 1.25rem;
}

由于我们滚动的时候,需要让 header 保持不动,所以,需要给它设置 position: fixed;

1
2
3
4
5
6
7
8
header {
position: fixed;
top: 0;
width: 100%;
height: 125px;
padding: 0 10%;
background: #ffffff;
}

然后,为了避免 main 元素包含的内容被 header 覆盖,我们需要让 main 往下挪一些,同时给它设置一个 padding

1
2
3
4
main {
margin-top: 125px;
padding: 10px 10%;
}

OK,大概的调整就这些,我们现在来构思如何实现这个 Amazing 的效果。

按照使用 JavaScript 来实现这个效果的思想,那么,我们就需要先去计算一个百分比——也就是滚动的文章长度与总的剩余文章长度的比。

如下图所示:

Indicator JavaScript

假设我们文章的总长度为 a,以红色的线为界限,超出红线上的部分即为滚动的距离。那么,我们此刻滚动的距离为 xz 代表的是滚动的进度百分比,红线的总长度 y 即为我们的 Indicator 的总长度,也就是所谓的 100%。那么,我们需要的肯定是以下的关系:

1
x / a === z / y;

那么,计算出来的滚动的进度百分比 z 应该就是:

1
z = (x * y) / a;

好了,这是 JavaScript 的大概思路,我们已经了解了,我们先把这个结果放这儿,来看看如何使用 CSS 来实现。

在这里,我们需要用到两个 CSS 属性和一个 CSS 方法和一个 CSS 单位——linear-gradient, background-position 和 calc() 和 vh

简单的介绍一下这两个属性:

background-position 接受 1 到 4 个参数,默认值为 0% 0%。可以设置的值有 top right bottom left center,前四个值后面还可以跟具体的数值或者百分比。如果不加方向说明,直接设置数值或者百分比,位置则会以 left top 进行定位。注意:此处的 top right bottom left 定位是以 padding-box(即 padding 的外沿框) 进行定位的。
若要了解详细内容,可查看 background-position | MDN

关于 linear-gradient,即用来创建线性渐变。这个函数可接受多个参数值,其中,第一个表示渐变的方向,如果不写的话,默认为 to bottom,其余分别是 to top | left | right,后面的参数表示渐变的颜色,可以有多个颜色,每一个颜色后面还可以指定颜色的渐变位置。
注意:由于 linear-gradient 生成的是一个 CSS image 对象,所以,这个函数只能应用在需要的数据类型是 image 的属性(如 background-image, background)上。也就是说,如果在 color 或者 background-color 属性上应用这个函数的话,将会不起作用。
若要了解详细内容,可查看 linear-gradient | MDN

关于 calc() 函数,可以用来动态计算 CSS 的属性值。比如 calc(100px - 10px) 最终结果就为 90px;
注意:为了让这个函数向上兼容,以后可能会在 calc 的内部允许使用关键字(或者说变量),而这些关键字(或者变量)就包含连字符(即减号),所以,为了避免解析冲突,要在运算符的前后加上空格。其中,+ 和 - 前后必须加上空格,* 和 / 前后不要求,但为了一致性,最好也都加上空格。
若要了解详细内容,可查看 calc | MDN

关于 vh 单位,这个单位代表的意思即 ViewHeight,即浏览器可视区域的高度。与百分比类似,100vh = height: 100%
若要了解详细内容,可查看 length | MDN

那么,我们使用 CSS 实现的话,思路和 JavaScript 稍微有一点区别。但原理是一样的,我们仍然需要计算一个比例。

还是来看一张图:

Indicator CSS

我们将我们的我们让 Indicator 的宽度和 body 等宽(好吧,其实就是 100%,此处这样话会好理解一点),然后,注意到黄色的三角形了吗,当我们向上滚动的时候,黄色的三角形也会跟着向上滚动,同时,它会和长度为 y 的 Indicator 发生交叉,Indicator 会从三角形截出一段距离 z。这时候,黄色三角形向上移动的距离是 x。那么,要达到 Indicator 的效果,我么肯定要满足下面的条件:

1
x / a === z / y;

所以,这样计算出来,我们的 z 就是:

1
z = (x * y) / a;

注意到没,其实和我们上面的使用 JavaScript 时的计算结果是一样的。所以,原理相同,我们只需要变通一下就好了。

那么,怎么用 CSS 写出来呢?

注意到黄色三角形了吗,它上面还有一个白色三角形,它们两个是对称的,由两种不同的颜色组成,正好可以将其作为我们的 Indicator 的高亮色和默认色。

我们把这两种颜色作为 body 的背景颜色,文章就在这个背景的上面,那么,我们滚动文章的时候,背景也会跟着动,就会有以上图中的效果了。

1
2
3
4
5
6
body {
background: linear-gradient(to right top, #0089f2 50%, #DDD 50%);
background-position: 0 125px;
background-size: 100% calc(100% - 100vh + 5px);
background-repeat: no-repeat;
}

注意到第一个属性的设置了吗,我们将两个颜色的位置都设置成了 50%,那么,这就相当于简便的过渡区域趋于无限小,看上去就相当于两种颜色累积在一起。

第二个属性,我们将背景的位置进行了更改,将其往下移动了 125px,这个距离正好是我们的 header 的高度。这样,在我们没有滚动的时候,Indicator 的进度将会是 0;。

第三个属性,看着这么长一个计算的等式,其实计算的是我们的剩余文章的长度。100% 即文章的总长度,100vh 即我们所看到的文章的长度,至于那个 5px 是什么,那是我们预留出来显示 Indicator 的高度。

OK,我们现在来看效果将会是下面这张图片显示的样子。

Indicator Effect

我们看到,当我们向上滚动的时候,headermain 的交界处的蓝色区域在变化,对,这就是我们将要实现的 Indicator 的原型。

那么,既然是一个 Indicator,那他就应该有一个 Indicator 的样子。我们需要把下面的那些全部遮住,只留出 Indicator 的那一条 progress bar。

所以,我们继续完善代码:

1
2
3
4
5
6
7
8
9
body:before {
content:'';
position: fixed;
top: 127px;
bottom: 0;
width: 100%;
z-index: -1;
background: white;
}

我们通过使用一个伪元素来把 body 中多余的背景给隐藏掉。

设置 top: 127px; 目的即让 Indicator 的高度为 2px,即到顶部的距离减去 header 的距离。

至此,我们使用 CSS 制作的 Indicator 就上大功告成了。

下面是我们实现的所有代码和效果:

See the Pen CSS only scroll indicator by Erichain (@Erichain) on CodePen.

本文参考自 https://codepen.io/MadeByMike/pen/ZOrEmr?editors=1100,我在此基础上对其进行了一些优化。如果有问题,欢迎在评论区留言,Feel Free To Ask。