A reading time estimator has been at the top of every article on this site for several years. It’s primary purpose is motivation: if it’s clear that reading an article should only take two minutes, readers are slightly more likely to complete it, and better able to manage their time doing so.
However, there are a few downsides to the current approach:
- the word count for each article is kept in a MySQL database. While this information can be updated quickly when the article changes, it makes sense to move the work of calculating the article’s words and estimated reading time from the server to the client, reducing the size and workload on the database.
- there’s no indication of how far an article has been progressed through, sapping motivation.
There are a number of reading gauges and timers out there, but every example I found had at least one drawback:
- use of a framework (usually jQuery).
- didn’t account for pictures, which slow reading time.
- provided inaccurate word counts (counting the entire page content, rather than an article section).
- used incorrect or non-semantic markup
- weren’t adaptive: most provided a fixed-position meter, which is fine for desktop screens but entirely inappropriate for mobile devices *.
- weren’t good at graceful degradation or accessibility
I’m currently in the process of designing and developing the next version of this site, and wanted to experiment with a solution that addressed all of these issues. This two-part series will look at how I did so: this article tackles getting and presenting an accurate word-count in text.
Corpus
To narrow the word count to a particular area on the page, I circumscribed it using established microdata and microformat standards:
<article role="article" itemscope itemtype="http://schema.org/BlogPosting" class="hentry">
<header>
<h1>Moby-Dick; or, The Whale</h1>
</header>
<div class="entry-content" itemprop="articleBody">
<p>Call me Ishmael…
</div>
</article>
With this done, we can focus the JavaScript on just the article, rather than trying to count the words of the entire page.
Script
The first part of the script sets up most of the variables:
var wpm = 200;
var elementsToCount = ["p","li","dd"];
var secsPerPic = 5;
var wordCount = 0;
var totalCount = 0;
var reportMethod = "roundedTime",
var articleText = document.getElementsByClassName("entry-content")[0];
estimateTime();
wpm
is the estimated reading speed of the average user, in words per minute. elementstocount
is an array of all the elements on the page that we want to count the text content of. (I have not included headings, as they are usually very short).
secsPerPic
is the number of seconds each picture on the page is expected to occupy the user’s attention, and articleText
finds our article body.
There are three options for reporting the length and reading time of the article:
roundedTime
("2 min to read")preciseTime
("00:45 to read")wordCount
("Article length: 800 words, 2 figures")
The first two estimates use the projected speed of the reader, while the last provides information about the article, allowing the reader to come to their own conclusion about how long it might take them to read.
Then, we call on an estimateTime
function:
function estimateTime(){
elementsToCount.forEach(addEmUp);
var pictures = articleText.getElementsByTagName("img");
completeCount = totalCount + (pictures.length * ((wpm / 60) * secsPerPic));
updateTime(completeCount / wpm, totalCount, pictures.length);
}
This function passes each of the elements to a word counting function, which I’ll address in a moment. The equivalent word count of the pictures is added to the total word count, and the reading time (the total number of words divided by the estimated reading time) is passed to the updateTime
function, together with the number of words and pictures in the article.
function addEmUp(element) {
var checkTag = articleText.getElementsByTagName(element);
for (var i=0; i < checkTag.length; i++) {
wordCount = checkTag[i].innerHTML.split(" ").length;
totalCount = totalCount + wordCount;
}
}
The addEmUp
function is pretty straightforward: it takes a supplied element, finds its text content, and counts the number of spaces in the text to arrive at a total number of words.
The total time estimate at this point is a decimal number (e.g. 2.5
, meaning an estimated two minute thirty second reading time). We need to convert that to a more useful and human readable sentence:
function updateTime(decimalTime, wordCount, picCount) {
var mins = Math.floor(Math.abs(decimalTime));
var secs = Math.floor((Math.abs(decimalTime) * 60) % 60);
var timeEst = mins + ":" + (secs < 10 ? "0" : "") + secs;
var timeestimate = document.createElement("p");
timeestimate.id = "timeestimate";
if (reportMethod == "roundedTime" || reportMethod == "preciseTime") {
var timeTag = document.createElement("time");
timeTag.setAttribute("datetime", mins + "m " + secs + "s");
if (reportMethod == "roundedTime") {
timeTag.innerHTML = (decimalTime > 0.5 ? Math.round(decimalTime) + " min" : secs + " secs");
} else {
timeTag.innerHTML = mins + ":" + (secs < 10 ? "0" : "") + secs;
}
timeestimate.appendChild(timeTag);
timeestimate.innerHTML += " to read";
}
if (reportMethod == "wordCount") {
timeestimate.innerHTML = "Article length: "+wordCount+" words";
if (picCount > 0) {
timeestimate.innerHTML += ", "+picCount+" figure"+(picCount > 1 ? "s" : "");
}
}
articleText.insertBefore(timeestimate, articleText.firstChild);
}
This function can deliver the estimated reading time in three different ways: either as a rounded number (“2 min to read”) with any content that takes less than half a minute to read reported as seconds, in a more precise 1:32
style, or as a report of the number of words and images in the article.
In the first two cases, a element presents a formatted datetime
duration that supplies the estimated reading time in machine-readable format, making the final HTML look like this:
<p id="timeestimate"><time datetime="0m 41s">1 min</time> to read</p>
Conclusion
This script gives a good approximation of the reading time for a piece of content, but it doesn’t provide any visual motivation to consume it. In the next article, I’ll show how to make this time estimate both interactive and graphical in a way that responds to the user.
*fixed position elements simply get in the way on mobile screens, reducing the amount of visible information; partly for that reason, mobile browsers have not done a good job of supporting them.
Photograph by Renaud Camus, used under a Creative Commons 2.0 Generic license.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/bdqGPB