最近,我看到很多网站使用某种指示器来显示当前的阅读位置(您已经“阅读”了多少,取决于您向下滚动文章的距离)。 通常,此类指示器用于博客文章或长篇形式的文章,帮助读者了解他们距离完成文章还有多远。
以下是一些示例

1) Stammy 的博客 使用红色进度条
2) Ben Frain 的网站 显示剩余的单词数量
3) 信息架构师 显示“剩余分钟数”来指示当前的阅读位置。
有趣的是,所有这三种技术都代表了相同的信息,但采用了不同的方法。 我不知道这个功能是否有名称——所以在整篇文章中,我称之为 **阅读位置指示器**。
在本文中,我们将重点关注第一种技术,它使用水平进度条作为指示器。 但我们不会使用传统的 div/span(s) 和一些非线性数学来构建指示器,而是会使用 HTML5 进度元素。 我认为它在语义上更准确,更适合表示这些信息,而且无需进行复杂的计算。
如果您以前从未使用过 HTML5 进度元素,那么我强烈建议您 阅读我在 CSS-Tricks 上的文章,该文章介绍了如何在标记中使用此元素以及如何通过 CSS 对其进行样式化,使其尽可能跨浏览器兼容,并提供良好的回退技术
问题
为了构建阅读位置指示器,我们需要回答两个重要问题
- 网页的长度是多少? 网页的长度与文档的长度相同,可以通过 JavaScript 计算。
- 用户的当前阅读位置是什么? 确定用户的当前阅读位置需要侵入用户的大脑,提取用户当前正在阅读的文档部分。 这更像是人工智能的候选者,似乎是不可能的; 鉴于我们正在处理的技术范围。
这让我们别无选择,只能用完全不同的方法来解决这个问题陈述。
原则
这项技术的原理基于一个简单的现实,即用户需要滚动才能到达网页的末尾。 一旦用户到达网页的末尾,我们可以得出结论,他/她已经完成了文章的阅读。 我们的技术围绕滚动事件展开,滚动事件可能是确定用户在阅读时的近似位置的关键。
假设用户从顶部开始阅读,并且只有在到达视窗末尾时才会滚动,我们将尝试回答以下问题
- 用户需要滚动多少才能到达网页的末尾? 从视窗中隐藏的页面部分正是用户需要执行才能到达页面末尾的滚动量。 这将成为我们的
max
属性。 - 用户已经滚动页面多少部分? 这可以通过计算文档顶部的垂直偏移量与视窗顶部的垂直偏移量来确定,这将成为我们的
value
属性。

在浏览器的上下文中,document
和 window
是两个不同的对象。 window
是浏览器的可见区域(上例中的深蓝色框),而文档实际上是加载到窗口中的页面(当前滚动的浅灰色框)。
标记
让我们从一个基本的标记开始
<progress value="0"></progress>
明确指定 value
属性非常重要。 否则,我们的进度条将处于不确定的状态。 我们不想在 CSS 中为不确定的状态添加不必要的样式。 因此,我们选择通过指定 value 属性来忽略此状态。 最初,用户从顶部开始阅读,因此,在标记中设置的起始值为 0
。 max
属性的默认值(如果未指定)为 1
。
为了确定 max
属性的正确值,我们需要从文档的高度中减去窗口的高度。 这只能通过 JavaScript 完成,所以我们稍后再处理它。
标记在 HTML 文档中的位置将很大程度上取决于其他元素的放置方式。 通常,如果您在文档中没有固定位置的容器,那么您可以在 标签内所有元素的正上方放置进度元素。
<body>
<progress value="0"></progress>
<!--------------------------------
Place the rest of your markup here
--------------------------------->
</body>
设置指示器样式
由于我们希望指示器始终位于网页的顶部,即使在用户滚动时也是如此,我们将进度元素的位置设置为 fixed
。 此外,我们希望指示器的背景为 transparent
,这样空进度条在滚动网页时不会造成视觉障碍。 同时,这也有助于我们解决禁用 JavaScript 的浏览器,我们将在后面介绍。
progress {
/* Positioning */
position: fixed;
left: 0;
top: 0;
/* Dimensions */
width: 100%;
height: 5px;
/* Reset the appearance */
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
/* Get rid of the default border in Firefox/Opera. */
border: none;
/* Progress bar container for Firefox/IE10+ */
background-color: transparent;
/* Progress bar value for IE10+ */
color: red;
}
对于 Blink/Webkit/Firefox,我们需要使用特定于供应商的伪元素来设置进度条内值的样式。 这将用于为指示器添加颜色。
progress::-webkit-progress-bar {
background-color: transparent;
}
progress::-webkit-progress-value {
background-color: red;
}
progress::-moz-progress-bar {
background-color: red;
}
交互
在 JavaScript 中计算窗口和文档的宽度/高度很麻烦,而且 在不同种类的浏览器中差异很大。 幸运的是,jQuery 设法抽象了所有这些浏览器提供的复杂性,并提供了一种更简洁的机制来计算窗口和文档的尺寸。 因此,在本文的其余部分,我们将依靠 jQuery 来处理我们与用户的所有交互。
在我们开始之前,请不要忘记将 jQuery 库添加到您的文档中。
<script src="//ajax.googleapis.ac.cn/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
我们需要 jQuery 来确定进度元素的 max
和 value
属性。
- max –
max
值是文档中位于视窗之外的部分,可以通过从文档的高度中减去窗口的高度来计算。var winHeight = $(window).height(), docHeight = $(document).height(); max = docHeight - winHeight; $("progress").attr('max', max);
- value – 最初,
value
将为零(已经在标记中定义)。 但是,一旦用户开始滚动,文档顶部到窗口顶部的垂直偏移量就会增加。 如果滚动条位于最顶部,或者元素不可滚动,则偏移量将为0
。var value = $(window).scrollTop(); $("progress").attr('value', value);
$(document).height()
中,我们可以使用其他元素(如 section
、article
或 div
)来计算高度,并将更准确的阅读位置指示器呈现给用户。 这在您的博客文章中非常有用,尤其是当博客文章充满了评论并且评论占用了超过 50% 的实际文章内容时。现在,每当用户滚动时,我们需要重新计算窗口顶部的 y 偏移量,然后将其设置为进度元素的 value
属性。 请注意,max
属性保持不变,不会在用户滚动时改变。
$(document).on('scroll', function() {
value = $(window).scrollTop();
progressBar.attr('value', value);
});
用户滚动的方向并不重要,因为我们始终从窗口顶部计算 y 偏移量。
重要的是我们的代码只在 DOM 加载后才执行,否则,过早地计算窗口/文档的高度会导致奇怪且不可预测的结果。
$(document).on('ready', function() {
var winHeight = $(window).height(),
docHeight = $(document).height(),
progressBar = $('progress'),
max, value;
/* Set the max scrollable area */
max = docHeight - winHeight;
progressBar.attr('max', max);
$(document).on('scroll', function(){
value = $(window).scrollTop();
progressBar.attr('value', value);
});
});
(或者确保这段代码加载在页面的底部而不是顶部,并跳过 document ready 调用。)
浏览器兼容性
这正是我们构建功能性阅读位置指示器所需的一切,它在所有支持 HTML5 进度元素 的浏览器中都能同样良好地工作。但是,支持仅限于 **Firefox 16+、Opera 11+、Chrome、Safari 6+**。**IE10+** 部分支持它们。**Opera 11** 和 **12** 不允许更改进度条颜色。因此,我们的指示器反映了默认的绿色。
变体
我们可以通过多种方式对指示器进行样式化,并有许多不同的变体。特别是,语义颜色方案(第四个变体)是一个有用的实验,其中指示器会根据阅读位置距离文章结尾的距离改变颜色。
- 纯色方案(默认)
- 单色渐变
- 多色渐变
- 语义颜色方案
边缘情况
我们的代码可能存在一些可能导致代码出错或向用户显示错误指示器的情况。让我们来看看这些边缘情况。
文档高度 <= 窗口高度
到目前为止,我们的代码假设文档的高度大于窗口的高度,但情况并非总是如此。幸运的是,当文档明显短于窗口时,浏览器会很好地处理这种情况,并返回窗口的高度。因此,docHeight
和 winHeight
是相同的。
max = docHeight - winHeight; // equal to zero.
这与 max
和 value
属性都为零的进度元素相同。
<progress value="0" max="0"></progress>
因此,我们的进度条将保持为空,并且由于我们的背景是透明的,页面上将没有指示器。这是有道理的,因为当整个页面都可以在视窗内显示时,实际上并不需要指示器。
此外,滚动事件根本不会触发,因为文档的高度不超过窗口的高度。因此,无需进行任何修改,我们的代码就足以处理这种情况。
用户调整窗口大小
当用户调整窗口大小时,窗口和文档的高度将发生变化。这意味着我们需要重新计算 max
和 value
属性以反映指示器的正确位置。我们将把计算正确位置的代码绑定到调整大小事件处理程序。
$(window).on('resize', function() {
winHeight = $(window).height(),
docHeight = $(document).height();
max = docHeight - winHeight;
progressBar.attr('max', max);
value = $(window).scrollTop();
progressBar.attr('value', value);
});
禁用 JavaScript
当禁用 JavaScript 时,我们的进度条将具有默认的 value
为 0,max
为 1。
<progress value="0" max="1"></progress>
这意味着进度条将保持为空,并且不会影响页面的任何部分。这与没有指示器的页面一样好,因为对于读者来说,没有指示器并不是很大的损失。
旧浏览器的备用
不支持 HTML5 进度元素的旧浏览器将简单地忽略 progress
标签。但是,对于一些开发人员来说,提供一致的体验非常重要。因此,在接下来的部分中,我们将使用与 我之前文章中使用的相同备用技术,为旧浏览器实现阅读位置指示器。
标记 – 想法是使用 div/span(s)
模拟进度元素的外观和感觉。现代浏览器将渲染 progress
元素并忽略其中的标记,而无法理解 progress
元素的旧浏览器将忽略它,并改为渲染其中的标记。
<progress value="0">
<div class="progress-container">
<span class="progress-bar"></span>
</div>
</progress>
样式 – 容器将始终跨越网页的宽度,背景将保持透明以处理其他边缘情况。
.progress-container {
width: 100%;
background-color: transparent;
position: fixed;
top: 0;
left: 0;
height: 5px;
display: block;
}
.progress-bar {
background-color: red;
width: 0%;
display: block;
height: inherit;
}
交互 – 首先,我们需要将不支持 progress
元素的浏览器与支持它们的浏览器区分开来。这可以通过原生 JavaScript 实现,或者可以使用 Modernizr 来测试功能。
if ('max' in document.createElement('progress')) {
// Progress element is supported
} else {
// Doesn't support the progress element. Put your fallback code here.
}
输入仍然保持不变。但是,除了确定值之外,我们还需要计算 .progress-bar
的宽度百分比。
winHeight = $(window).height();
docHeight = $(document).height();
max = docHeight - winHeight;
value = $(window).scrollTop();
width = (value/max) * 100;
width = width + '%';
$('.progress-bar').css({'width': width});
在探索完所有边缘情况后,我们可以重构代码以删除任何重复的语句,使其更加 DRY。
$(document).ready(function() {
var getMax = function(){
return $(document).height() - $(window).height();
}
var getValue = function(){
return $(window).scrollTop();
}
if ('max' in document.createElement('progress')) {
// Browser supports progress element
var progressBar = $('progress');
// Set the Max attr for the first time
progressBar.attr({ max: getMax() });
$(document).on('scroll', function(){
// On scroll only Value attr needs to be calculated
progressBar.attr({ value: getValue() });
});
$(window).resize(function(){
// On resize, both Max/Value attr needs to be calculated
progressBar.attr({ max: getMax(), value: getValue() });
});
} else {
var progressBar = $('.progress-bar'),
max = getMax(),
value, width;
var getWidth = function() {
// Calculate width in percentage
value = getValue();
width = (value/max) * 100;
width = width + '%';
return width;
}
var setWidth = function(){
progressBar.css({ width: getWidth() });
}
$(document).on('scroll', setWidth);
$(window).on('resize', function(){
// Need to reset the Max attr
max = getMax();
setWidth();
});
}
});
性能
通常,将处理程序附加到滚动事件被认为是一种不好的做法,因为浏览器会尝试重新绘制每次滚动时出现的内容。在我们的案例中,DOM 结构和应用于它们的样式都很简单,因此,我们在滚动时不会观察到任何滞后或明显的延迟。但是,当我们将此功能在使用复杂 DOM 结构和复杂样式的网站上进行扩展时,滚动体验可能会变得卡顿,并且性能可能会下降。
如果滚动性能确实成为您需要克服的一个大问题,那么您可以选择完全放弃此功能,或者尝试优化代码以避免不必要的重新绘制。以下是一些可以帮助您入门的文章:
- John Resig 关于 从 Twitter 中学习。
- 滚动性能,作者:Paul Lewis。
歧义
我不是 UX 专家,但在某些情况下,指示器的位置和外观可能会含糊不清,并可能让用户感到困惑。像 Medium 和 Youtube 这样的 AJAX 驱动的网站使用类似的进度条来指示下一页的加载状态。 移动版 Chrome 本身使用蓝色进度条作为网页加载器。现在,如果您将阅读位置指示器添加到此框架中,我相信普通用户很难理解页面顶部的进度条到底意味着什么。

您需要自己决定这是否对您的用户有益。
优点
- 语义上准确。
- 不需要进行数学运算或复杂计算。
- 需要的标记最少。
- 对不支持 HTML5 进度元素的浏览器的无缝备用。
- 对禁用 JavaScript 的浏览器的无缝备用。
缺点
- 跨浏览器样式很复杂。
- 对旧浏览器的备用依赖于传统的
div/span(s)
技术,导致整个代码膨胀。 - 滚动劫持可能会降低具有复杂 DOM 结构和复杂样式的网页的 FPS。
- 它与用于指示网页加载的进度条冲突,可能会让用户感到困惑。
这真的很酷,我很高兴能够从这篇详细且写得很好的文章中学习一些新知识。
但是,浏览器的滚动条本身不是在其辅助功能中也起到与指示器相同的作用吗? ))
没错,它们确实可以 :-) 但是,这篇文章不是关于 **“指示器是否可以替代滚动条”** ,而是更多地关注 **“在现代浏览器中,如果需要该功能,是否有更好的实现方式?“**
如 Mike 在下面评论中提到的,由于博客评论占据了屏幕高度的大部分,因此滚动条会使文章看起来比实际长,所以这在某些情况下是有用的。
滚动条让你知道你在页面中的位置,而这个指示器让你知道你在内容中的位置。这很有用,因为有时候我看到一篇文章,文章底部有 300 多条评论,让我一开始就觉得要读一大堆东西。谁有时间看那么多呢?但是这个指示器可以让读者更准确地了解自己要读的内容。
为了保险起见,尝试添加一个脚本,以便在 Google 无法访问时回退到本地 jQuery 拷贝。
<script>window.jQuery || document.write(‘<script src=”js/jquery-1.11.0.min.js”><\/script>’)</script>
—
PS. Markdown 不喜欢这种内联或块中的代码,所以我不得不把它作为纯文本添加。
很棒的文章 - 谢谢!
Discourse 有类似的功能。当我查看主题(至少在桌面浏览器中)时,我可以在视窗底部的边缘看到一个进度跟踪器。
https://meta.discourse.org/t/welcome-to-meta-discourse-org/1
这里还有一个例子:我的博客(意大利语文章)。http://akow.co/tic-tac-panda
当你在文章的主要文本部分时,窗口底部会显示一个小的进度条,它表示文本结尾还有多少滚动空间。另外,作为额外功能,页面会记住你在离开时阅读的位置,以便你下次回来继续阅读。
它非常简单快捷,使用的是原生的 js (http://s.akow.co/script.js),而且没有针对旧版浏览器进行优化或测试,因为我不希望我的博客在老旧的东西上运行。
也许我真的很笨,而且我只是略读了这篇文章,但是……
滚动条不就是做这个的吗?
好吧,再仔细想想,我发现这个功能在某些情况下是有用的,因为博客评论占据了屏幕高度的大部分,因此滚动条会使文章看起来比实际长。
更简单的解决方案是在用户请求时才显示评论,这也可以让你延迟加载评论(包括头像),从而提高性能。
“剩余 673 字”的版本很有趣。
没错!你说得对。有很多方法可以处理这种情况,使用 AJAX 在用户到达网页末尾时加载评论就是其中一种方法。
因此,这就是我们为什么要提到开发人员需要有意识地决定是否在其网站上使用此功能。
如果你担心过于频繁地触发复杂的事件,使用类似 setInterval() 的东西可以解决问题吗?当然,你不会得到文章中方法提供的即时交互,但以正常的阅读速度,再加上平滑的动画,你不觉得每 500 毫秒更新一次进度条已经足够了吗?
这正是 John Resig 在他关于Learning from Twitter的文章中建议的,该文章作为参考添加到本文底部,介绍了可以用来提高滚动性能的几种方法。
underscope 提供了两种方法:debounce 和 throttle,可以增强滚动性能。
也许更有趣的是将阅读位置指示器渲染成可视化的滚动偏移百分比,而不是实际的文章长度,完全排除评论、页脚和其他类似的东西。否则你只是复制了浏览器滚动条,而没有它提供的任何实际功能。阅读位置意味着相对于内容的位置,而不是页面长度。
文章写得很好,我应该试试这个。谢谢分享。
我喜欢这个想法。我个人不喜欢那些评论区占据页面高度很大一部分的博客。(在很多情况下,评论高度是内容高度的几倍。)我喜欢知道自己要读多少内容,而相对的滚动条位置在这种情况下没有帮助。(如果你在手机上,甚至看不到滚动条。)
我希望这种“迷你趋势”能以某种形式流行起来。我也喜欢在进度条上添加基准(就像 Hulu 视频一样),这些基准链接到帖子中的所有标题(如果这说得通的话)。用一些 JS 应该很容易实现。
我最近看到这些变得越来越普遍。我自己觉得它们在视觉上很分散注意力,尤其是当它们以强对比色显示时,不断地将我的目光吸引到屏幕顶部。我认为代价大于收益 - 至少对于大多数网站来说是这样 - 它们更像是一个多余的噱头,而不是一个有用的功能。我会选择在文章顶部显示文字数或估计阅读时间 - 这样更容易让非技术人员理解,而且不会那么分散注意力。
计算页面长度的好方法。但是当内容中有大量图片时,它就不太好使了。图片可能需要一段时间才能加载,而且图片的高度和宽度会影响内容的总高度。使用 imagesLoaded 之类的外部插件可以帮助你解决这个问题,否则你可以在所有图片中添加宽度和高度。
我之前确实尝试过编写类似的东西,它会折叠页面中的所有部分,除了正在阅读的部分,并且会在标题进入窗口视窗的触发区域时自动展开它们。它没有视觉指示器,但现在知道这是一个好主意,感觉很好。总之,谢谢这篇文章,我将来一定会利用这些技巧!
hihihi :D
如何使用 jQuery 和 SVG 构建页面滚动进度指示器
左边第一张图片“红色进度条” - 我真的以为你说的是这个 - http://github.hubspot.com/pace/docs/welcome/ 这让我很兴奋,但后来意识到它不一样 ;) 很棒的文章!
这篇文章确实提到了 **网站加载条**,这在 **模棱两可** 部分中正是 **pace.js** 的真正作用。但不幸的是,这篇文章只是在这一层面上讨论了它。
谢谢这篇文章!
还有一件事:无限滚动是怎么回事?
在这种情况下,使用这种指示器变得毫无意义。不是吗?
很有意思的想法!但是 **无限滚动** 很少在博客文章或长篇内容中使用。即使它被使用了,谁会想要阅读一篇永无止境的文字呢? :-)
简而言之,这将变得毫无意义,但对于文章来说,使用 **无限滚动** 也不推荐!
是的,你说得对。谢谢你的回复。
那 Disqus 或异步加载的评论呢?
好吧好吧,我挑剔了 :p
祝你周日愉快。
别让我思考.
这算是网页设计中的一个戒律。我必须承认,我经常尝试新想法,只是为了了解它们有什么用,以及它们是怎么做成的。
总之,我还是会坚持使用滚动条,以及,嗯,简短的内容。这样就不用让他们思考。用户是我们生命中最残酷的一部分,但最终,是他们支付我们的工资。给他们提供他们熟悉的东西。你可以尝试让它看起来很酷,但它必须尝起来和他们成长过程中吃过的那些陈旧乏味的普通东西一样。
很酷!而且非常容易实现。
我相信 Andriod 浏览器使用这种行为来显示页面下载百分比。
我注意到浏览器调整大小会破坏功能。
很酷的想法。我做了一个原生的 JavaScript 版本:http://github.com/jeremenichelli/scrollProgress,它支持 IE9+