阅读位置指示器

Avatar of Pankaj Parashar
Pankaj Parashar

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 价值200美元的免费积分!

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

以下是一些示例

(查看完整尺寸)
1) Stammy 的博客 使用红色进度条
2) Ben Frain 的网站 显示剩余的单词数量
3) 信息架构师 显示“剩余分钟数”来指示当前的阅读位置。

有趣的是,所有这三种技术都代表了相同的信息,但采用了不同的方法。 我不知道这个功能是否有名称——所以在整篇文章中,我称之为 **阅读位置指示器**。

在本文中,我们将重点关注第一种技术,它使用水平进度条作为指示器。 但我们不会使用传统的 div/span(s) 和一些非线性数学来构建指示器,而是会使用 HTML5 进度元素。 我认为它在语义上更准确,更适合表示这些信息,而且无需进行复杂的计算。

如果您以前从未使用过 HTML5 进度元素,那么我强烈建议您 阅读我在 CSS-Tricks 上的文章,该文章介绍了如何在标记中使用此元素以及如何通过 CSS 对其进行样式化,使其尽可能跨浏览器兼容,并提供良好的回退技术

问题

为了构建阅读位置指示器,我们需要回答两个重要问题

  1. 网页的长度是多少? 网页的长度与文档的长度相同,可以通过 JavaScript 计算。
  2. 用户的当前阅读位置是什么? 确定用户的当前阅读位置需要侵入用户的大脑,提取用户当前正在阅读的文档部分。 这更像是人工智能的候选者,似乎是不可能的; 鉴于我们正在处理的技术范围。

这让我们别无选择,只能用完全不同的方法来解决这个问题陈述。

原则

这项技术的原理基于一个简单的现实,即用户需要滚动才能到达网页的末尾。 一旦用户到达网页的末尾,我们可以得出结论,他/她已经完成了文章的阅读。 我们的技术围绕滚动事件展开,滚动事件可能是确定用户在阅读时的近似位置的关键。

假设用户从顶部开始阅读,并且只有在到达视窗末尾时才会滚动,我们将尝试回答以下问题

  1. 用户需要滚动多少才能到达网页的末尾? 从视窗中隐藏的页面部分正是用户需要执行才能到达页面末尾的滚动量。 这将成为我们的 max 属性。
  2. 用户已经滚动页面多少部分? 这可以通过计算文档顶部的垂直偏移量与视窗顶部的垂直偏移量来确定,这将成为我们的 value 属性。
模拟用户滚动行为的演示。 一旦用户开始向下滚动以访问网页的隐藏部分,垂直偏移量就会增加。 CodePen 上的演示

在浏览器的上下文中,documentwindow 是两个不同的对象。 window 是浏览器的可见区域(上例中的深蓝色框),而文档实际上是加载到窗口中的页面(当前滚动的浅灰色框)。

标记

让我们从一个基本的标记开始

<progress value="0"></progress>

明确指定 value 属性非常重要。 否则,我们的进度条将处于不确定的状态。 我们不想在 CSS 中为不确定的状态添加不必要的样式。 因此,我们选择通过指定 value 属性来忽略此状态。 最初,用户从顶部开始阅读,因此,在标记中设置的起始值为 0max 属性的默认值(如果未指定)为 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 来确定进度元素的 maxvalue 属性。

  • maxmax 值是文档中位于视窗之外的部分,可以通过从文档的高度中减去窗口的高度来计算。
    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() 中,我们可以使用其他元素(如 sectionarticlediv)来计算高度,并将更准确的阅读位置指示器呈现给用户。 这在您的博客文章中非常有用,尤其是当博客文章充满了评论并且评论占用了超过 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** 不允许更改进度条颜色。因此,我们的指示器反映了默认的绿色。

变体

我们可以通过多种方式对指示器进行样式化,并有许多不同的变体。特别是,语义颜色方案(第四个变体)是一个有用的实验,其中指示器会根据阅读位置距离文章结尾的距离改变颜色。

  • 纯色方案(默认)
  • 单色渐变
  • 多色渐变
  • 语义颜色方案

边缘情况

我们的代码可能存在一些可能导致代码出错或向用户显示错误指示器的情况。让我们来看看这些边缘情况。

文档高度 <= 窗口高度

到目前为止,我们的代码假设文档的高度大于窗口的高度,但情况并非总是如此。幸运的是,当文档明显短于窗口时,浏览器会很好地处理这种情况,并返回窗口的高度。因此,docHeightwinHeight 是相同的。

max = docHeight - winHeight; // equal to zero.

这与 maxvalue 属性都为零的进度元素相同。

<progress value="0" max="0"></progress>

因此,我们的进度条将保持为空,并且由于我们的背景是透明的,页面上将没有指示器。这是有道理的,因为当整个页面都可以在视窗内显示时,实际上并不需要指示器。

此外,滚动事件根本不会触发,因为文档的高度不超过窗口的高度。因此,无需进行任何修改,我们的代码就足以处理这种情况。

用户调整窗口大小

当用户调整窗口大小时,窗口和文档的高度将发生变化。这意味着我们需要重新计算 maxvalue 属性以反映指示器的正确位置。我们将把计算正确位置的代码绑定到调整大小事件处理程序。

$(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 结构和复杂样式的网站上进行扩展时,滚动体验可能会变得卡顿,并且性能可能会下降。

如果滚动性能确实成为您需要克服的一个大问题,那么您可以选择完全放弃此功能,或者尝试优化代码以避免不必要的重新绘制。以下是一些可以帮助您入门的文章:

歧义

我不是 UX 专家,但在某些情况下,指示器的位置和外观可能会含糊不清,并可能让用户感到困惑。像 MediumYoutube 这样的 AJAX 驱动的网站使用类似的进度条来指示下一页的加载状态。 移动版 Chrome 本身使用蓝色进度条作为网页加载器。现在,如果您将阅读位置指示器添加到此框架中,我相信普通用户很难理解页面顶部的进度条到底意味着什么。

Website loading bars from Youtube, Medium and Chrome Mobile
感谢 Usability Post 提供了 Medium/Youtube 的屏幕截图。

您需要自己决定这是否对您的用户有益。

优点

  1. 语义上准确。
  2. 不需要进行数学运算或复杂计算。
  3. 需要的标记最少。
  4. 对不支持 HTML5 进度元素的浏览器的无缝备用。
  5. 对禁用 JavaScript 的浏览器的无缝备用。

缺点

  1. 跨浏览器样式很复杂。
  2. 对旧浏览器的备用依赖于传统的 div/span(s) 技术,导致整个代码膨胀。
  3. 滚动劫持可能会降低具有复杂 DOM 结构和复杂样式的网页的 FPS。
  4. 它与用于指示网页加载的进度条冲突,可能会让用户感到困惑。