Leseposition-Anzeiger

Avatar of Pankaj Parashar
Pankaj Parashar am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

In letzter Zeit habe ich einige Websites gesehen, die eine Art von Anzeiger haben, um die aktuelle Leseposition anzuzeigen (wie viel Sie "gelesen" haben, je nachdem, wie weit Sie nach unten auf einen Artikel gescrollt sind). Im Allgemeinen werden solche Anzeiger auf Blog-Posts oder langen Artikeln verwendet und helfen den Lesern zu verstehen, wie weit sie davon entfernt sind, den Artikel zu beenden.

Hier sind einige Beispiele

(Gesamtgröße anzeigen)
1) Stammys Blog verwendet eine rote Fortschrittsleiste
2) Ben Frains Website zeigt die verbleibenden Wörter an
3) Information Architects zeigt "Minuten übrig" an, um die aktuelle Leseposition anzuzeigen.

Interessanterweise stellen alle drei Techniken dieselbe Information dar, jedoch mit einem anderen Ansatz. Ich weiß nicht, ob diese Funktion einen Namen hat – daher nenne ich sie im gesamten Artikel **Leseposition-Anzeiger**.

In diesem Artikel konzentrieren wir uns auf die erste Technik, die eine horizontale Fortschrittsleiste als Anzeiger verwendet. Anstatt jedoch traditionelle div/span(s) und nichtlineare Mathematik zu verwenden, um den Anzeiger zu erstellen, verwenden wir das HTML5-Fortschrittselement. Meiner Meinung nach ist dies semantisch genauer und besser geeignet, diese Information darzustellen, und das ganz ohne komplexe Berechnungen.

Wenn Sie das HTML5-Fortschrittselement noch nie zuvor verwendet haben, empfehle ich Ihnen dringend, meinen Artikel auf CSS-Tricks zu lesen, der Ihnen eine Einführung gibt, wie Sie dieses Element in Ihrem Markup verwenden und es per CSS so browserübergreifend wie möglich mit ordentlichen Fallback-Techniken gestalten.

Das Problem

Um einen Leseposition-Anzeiger zu erstellen, müssen wir zwei wichtige Fragen beantworten

  1. Wie lang ist die Webseite? Die Länge der Webseite ist gleich der Länge des Dokuments, die per JavaScript berechnet werden kann.
  2. Was ist die aktuelle Leseposition des Benutzers? Die aktuelle Leseposition des Benutzers zu ermitteln, würde erfordern, in den Verstand des Benutzers einzudringen, um den Teil des Dokuments zu extrahieren, den der Benutzer gerade liest. Das erscheint eher als ein Kandidat für künstliche Intelligenz und scheint angesichts des Umfangs der von uns behandelten Technologien unmöglich.

Dies lässt uns keine andere Wahl, als dieses Problem mit einem völlig anderen Ansatz anzugehen.

Prinzip

Das Prinzip hinter dieser Technik basiert auf der einfachen Tatsache, dass der Benutzer scrollen muss, um das Ende der Webseite zu erreichen. Sobald der Benutzer das Ende der Webseite erreicht hat, können wir schlussfolgern, dass er/sie den Artikel beendet hat. Unsere Technik dreht sich um das Scroll-Ereignis, das wahrscheinlich der Schlüssel zur Bestimmung einer ungefähren Position des Benutzers beim Lesen ist.

Unter der Annahme, dass der Benutzer von oben beginnt und erst scrollt, wenn er/sie das Ende des Viewports erreicht hat, werden wir versuchen, die folgenden Fragen zu beantworten

  1. Wie viel muss der Benutzer scrollen, um das Ende der Webseite zu erreichen? Der Teil der Seite, der vom Viewport verborgen ist, ist genau der Scroll-Betrag, den der Benutzer ausführen muss, um das Ende der Seite zu erreichen. Dies wird unser max-Attribut.
  2. Wie viel vom Seiteninhalt hat der Benutzer bereits gescrollt? Dies kann ermittelt werden, indem der vertikale Offset der Oberseite des Dokuments von der Oberseite des Fensters berechnet wird, was unser value-Attribut wird.
Eine Demo, die das Scroll-Verhalten des Benutzers simuliert. Sobald der Benutzer beginnt, nach unten zu scrollen, um den verborgenen Teil der Webseite aufzurufen, erhöht sich der vertikale Offset. Demo auf CodePen

Im Kontext des Browsers sind document und window zwei verschiedene Objekte. window ist der sichtbare Bereich des Browsers (dicker blauer Kasten im obigen Beispiel) und das Dokument ist tatsächlich die Seite, die im Fenster geladen wird (dünner grauer Kasten, der gerade gescrollt wird).

Markup

Beginnen wir mit einem einfachen Markup

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

Es ist wichtig, das value-Attribut explizit anzugeben. Andernfalls befindet sich unsere Fortschrittsleiste im unbestimmten Zustand. Wir möchten keine unnötigen Stile in CSS für den unbestimmten Zustand hinzufügen. Daher entscheiden wir uns, diesen Zustand durch Angabe des value-Attributs zu ignorieren. Anfangs beginnt der Benutzer mit dem Lesen von oben, daher ist der Anfangswert im Markup auf 0 gesetzt. Der Standardwert des max-Attributs (falls nicht angegeben) ist 1.

Um den korrekten Wert für das max-Attribut zu ermitteln, müssen wir die Höhe des Fensters von der Höhe des Dokuments subtrahieren. Dies kann nur per JavaScript erfolgen, daher kümmern wir uns später darum.

Die Platzierung des Markups im HTML-Dokument hängt stark davon ab, wie die übrigen Elemente platziert sind. Wenn Sie in Ihrem Dokument keine Elemente mit fester Position haben, können Sie das Fortschrittselement direkt über allen anderen Elementen innerhalb des -Tags platzieren.

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

  <!--------------------------------
  Place the rest of your markup here
  --------------------------------->
</body>

Gestaltung des Anzeigers

Da wir möchten, dass unser Anzeiger immer auf der Webseite verbleibt, auch wenn der Benutzer scrollt, werden wir das Fortschrittselement als fixed positionieren. Zusätzlich möchten wir, dass der Hintergrund unseres Anzeigers transparent ist, damit eine leere Fortschrittsleiste beim Scrollen durch die Webseite keine visuelle Beeinträchtigung darstellt. Gleichzeitig hilft uns dies auch bei Browsern mit deaktiviertem JavaScript, was wir später behandeln werden.

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;
}

Für Blink/Webkit/Firefox müssen wir browserspezifische Pseudo-Elemente verwenden, um den Wert innerhalb der Fortschrittsleiste zu gestalten. Dies wird verwendet, um unserem Anzeiger Farbe zu verleihen.

progress::-webkit-progress-bar {
  background-color: transparent;
}

progress::-webkit-progress-value {
  background-color: red;
}

progress::-moz-progress-bar {
  background-color: red;
}

Interaktion

Die Berechnung der Breite/Höhe von Fenster und Dokument in JavaScript ist umständlich und variiert stark zwischen verschiedenen Browsern. Glücklicherweise abstrahiert jQuery all die Komplexität, die diese Browser bieten, und bietet einen wesentlich saubereren Mechanismus zur Berechnung der Fenster- und Dokumentdimensionen. Daher werden wir uns für den Rest des Artikels auf jQuery verlassen, um alle unsere Interaktionen mit dem Benutzer zu verwalten.

Vergessen Sie nicht, die jQuery-Bibliothek zu Ihrem Dokument hinzuzufügen, bevor wir beginnen.

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>

Wir benötigen jQuery, um das max- und das value-Attribut unseres Fortschrittselements zu ermitteln.

  • max – Der max-Wert ist der Teil des Dokuments, der sich außerhalb des Viewports befindet und durch Subtraktion der Fensterhöhe von der Dokumenthöhe berechnet werden kann.
    var winHeight = $(window).height(),   docHeight = $(document).height();   max = docHeight - winHeight; $("progress").attr('max', max);
  • value – Anfangs ist value Null (bereits im Markup definiert). Sobald der Benutzer jedoch zu scrollen beginnt, erhöht sich der vertikale Offset der Oberseite des Dokuments von der Oberseite des Fensters. Wenn sich die Scrollleiste ganz oben befindet oder das Element nicht scrollbar ist, beträgt der Offset 0.
    var value = $(window).scrollTop(); $("progress").attr('value', value);
Anstatt document in $(document).height() zu verwenden, können wir andere Elemente wie section, article oder div verwenden, die den Inhalt des Artikels enthalten, um die Höhe zu berechnen und dem Benutzer eine genauere Darstellung des Leseposition-Anzeigers zu bieten. Dies ist sehr nützlich, wenn Sie einen Blogbeitrag haben, der mit Kommentaren gefüllt ist und mehr als 50 % des eigentlichen Artikels ausmacht.

Jedes Mal, wenn der Benutzer scrollt, müssen wir den y-Offset vom oberen Fensterrand neu berechnen und ihn dann dem value-Attribut des Fortschrittselements zuweisen. Beachten Sie, dass das max-Attribut gleich bleibt und sich beim Scrollen des Benutzers nicht ändert.

$(document).on('scroll', function() {
  value = $(window).scrollTop();
  progressBar.attr('value', value);
});

Die Richtung, in der der Benutzer scrollt, ist nicht wichtig, da wir den y-Offset immer vom oberen Fensterrand berechnen.

Es ist wichtig, dass unser Code nur ausgeführt wird, wenn das DOM geladen ist, da eine vorzeitige Berechnung der Fenster-/Dokumenthöhe zu seltsamen und unvorhersehbaren Ergebnissen führen kann.

$(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);
  });
});

(Oder stellen Sie sicher, dass dieser Code am Ende der Seite geladen wird und überspringen Sie den Document-Ready-Aufruf.)

Browserkompatibilität

Das ist alles, was wir brauchen, um einen funktionierenden Leseposition-Anzeiger zu erstellen, der in allen Browsern, die das HTML5-Fortschrittselement unterstützen, gut funktioniert. Die Unterstützung ist jedoch auf **Firefox 16+, Opera 11+, Chrome, Safari 6+** beschränkt. **IE10+** unterstützt sie teilweise. **Opera 11** und **12** erlauben keine Farbänderung der Fortschrittsleiste. Daher spiegelt unser Anzeiger die standardmäßige grüne Farbe wider.

Varianten

Es gibt einige Variationen, wie wir den Anzeiger gestalten können. Insbesondere das semantische Farbschema (vierte Variante) ist ein nützliches Experiment, bei dem sich der Anzeiger basierend auf der Nähe der Leseposition zum Ende des Artikels ändert.

  • Flaches Farbschema (Standard)
  • Einfarbiger Verlauf
  • Mehrfarbiger Verlauf
  • Semantisches Farbschema

Randfälle

Es gibt einige Szenarien, in denen unser Code potenziell brechen oder dem Benutzer einen falschen Anzeiger präsentieren kann. Betrachten wir diese Randfälle

Dokumenthöhe <= Fensterhöhe

Bisher geht unser Code davon aus, dass die Höhe des Dokuments größer ist als die Höhe des Fensters, was nicht immer der Fall sein mag. Glücklicherweise verhalten sich Browser in dieser Situation sehr gut, indem sie die Höhe des Fensters zurückgeben, wenn das Dokument sichtbar kürzer als das Fenster ist. Daher sind docHeight und winHeight identisch.

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

Dies ist so gut wie eine Fortschrittsleiste mit max- und value-Attributen von Null.

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

Daher bleibt unsere Fortschrittsleiste leer und da unser Hintergrund transparent ist, gibt es keinen Anzeiger auf der Seite. Das ist sinnvoll, denn wenn die gesamte Seite in den Viewport passt, gibt es wirklich keinen Bedarf für einen Anzeiger.

Außerdem wird das Scroll-Ereignis überhaupt nicht ausgelöst, da die Dokumenthöhe die Fensterhöhe nicht überschreitet. Daher ist unser Code ohne Änderungen robust genug, um diesen Randfall zu behandeln.

Benutzer ändert die Fenstergröße

Wenn der Benutzer das Fenster vergrößert, ändern sich die Höhe des Fensters und des Dokuments. Das bedeutet, dass wir die Attribute max und value neu berechnen müssen, um die richtige Position des Anzeigers widerzuspiegeln. Wir binden den Code, der die richtige Position berechnet, an den Resize-Event-Handler.

$(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 ist deaktiviert

Wenn JavaScript deaktiviert ist, hat unsere Fortschrittsleiste den Standardwert value als 0 und max als 1.

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

Das bedeutet, dass die Fortschrittsleiste leer bleibt und keinen Teil der Seite beeinträchtigt. Das ist in Ordnung, denn eine Seite ohne Anzeiger ist für den Leser kein großer Verlust.

Fallback für ältere Browser

Ältere Browser, die das HTML5-Fortschrittselement nicht unterstützen, ignorieren das progress-Tag einfach. Für einige Entwickler ist jedoch eine konsistente Benutzererfahrung wichtig. Daher werden wir im folgenden Abschnitt die gleiche Fallback-Technik anwenden, die in meinem vorherigen Artikel verwendet wurde, um den Leseposition-Anzeiger für ältere Browser zu implementieren.

Markup – Die Idee ist, das Aussehen und Gefühl des Fortschrittselements mit div/span(s) zu simulieren. Moderne Browser rendern das progress-Element und ignorieren den Inhalt darin, während ältere Browser, die das progress-Element nicht verstehen, es ignorieren und stattdessen den Inhalt darin rendern.

<progress value="0">
  <div class="progress-container">
    <span class="progress-bar"></span>
  </div>
</progress>

Styling – Der Container erstreckt sich immer über die gesamte Breite der Webseite und der Hintergrund bleibt transparent, um andere Randfälle zu behandeln.

.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;
}

Interaktion – Zuerst müssen wir Browser trennen, die das progress-Element nicht unterstützen, von Browsern, die es unterstützen. Dies kann entweder mit nativem JavaScript erreicht werden oder Sie können Modernizr verwenden, um das Feature zu testen.

if ('max' in document.createElement('progress')) {
  // Progress element is supported
} else {
  // Doesn't support the progress element. Put your fallback code here. 
}

Die Eingaben bleiben gleich. Aber zusätzlich zur Ermittlung des Werts müssen wir die Breite der .progress-bar in Prozent berechnen.

winHeight = $(window).height(); 
docHeight = $(document).height();

max = docHeight - winHeight;
value = $(window).scrollTop();

width = (value/max) * 100;
width = width + '%';
    
$('.progress-bar').css({'width': width});

Nachdem wir alle Randfälle untersucht haben, können wir den Code refaktorieren, um doppelte Anweisungen zu entfernen und ihn DRY-er zu machen.

$(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();
    });
  }
});

Performance

Im Allgemeinen gilt es als schlechte Praxis, Handler an das Scroll-Ereignis zu binden, da der Browser versucht, den erscheinenden Inhalt jedes Mal neu zu rendern, wenn Sie scrollen. In unserem Fall sind die DOM-Struktur und die darauf angewendeten Stile einfach, daher würden wir beim Scrollen keinen Lag oder spürbare Verzögerung beobachten. Wenn wir jedoch die Skala, auf der dieses Feature implementiert werden kann, auf Websites mit komplexen DOM-Strukturen und ausgefallenen Stilen erhöhen, kann das Scroll-Erlebnis ruckelig werden und die Leistung leiden.

Wenn die Scroll-Leistung wirklich zu einem großen Problem für Sie wird, können Sie entweder dieses Feature komplett entfernen oder versuchen, Ihren Code zu optimieren, um unnötige Neuzeichnungen zu vermeiden. Ein paar nützliche Artikel, um Ihnen den Einstieg zu erleichtern

Mehrdeutigkeit

Ich bin kein UX-Experte, aber in einigen Fällen können die Position und das Aussehen unseres Anzeigers mehrdeutig sein und den Benutzer potenziell verwirren. Ajax-gesteuerte Websites wie Medium, Youtube usw. verwenden eine ähnliche Fortschrittsleiste, um den Ladezustand der nächsten Seite anzuzeigen. Chrome für Mobilgeräte verwendet nativ eine blaue Fortschrittsleiste für den Webseiten-Loader. Wenn Sie nun den Leseposition-Anzeiger in diesen Rahmen einfügen, bin ich sicher, dass ein durchschnittlicher Benutzer Schwierigkeiten haben wird zu verstehen, was die Fortschrittsleiste am oberen Rand der Seite wirklich bedeutet.

Website loading bars from Youtube, Medium and Chrome Mobile
Dank an Usability Post für Screenshots von Medium/Youtube.

Sie müssen selbst entscheiden, ob die Verwendung dieser Funktion für Ihre Benutzer von Vorteil ist oder nicht.

Vorteile

  1. Semantisch korrekt.
  2. Keine Mathematik oder komplexe Berechnungen erforderlich.
  3. Minimaler Markup-Aufwand.
  4. Nahtloser Fallback für Browser ohne Unterstützung für das HTML5-Fortschrittselement.
  5. Nahtloser Fallback für Browser mit deaktiviertem JavaScript.

Nachteile

  1. Browserübergreifendes Styling ist komplex.
  2. Der Fallback für ältere Browser basiert auf der traditionellen div/span(s)-Technik, was den gesamten Code aufbläht.
  3. Scroll-Hijacking kann die FPS auf Webseiten mit komplexer DOM-Struktur und ausgefallenen Stilen potenziell reduzieren.
  4. Es kollidiert mit der Fortschrittsleiste, die zur Anzeige des Seitenladens verwendet wird, und kann Benutzer verwirren.