Add Medium read time to your Ghost blog

One of the great things about Medium is their automatically calculated 'Read Time', which quickly shows the reader how much time the article will take to read. This is something I really like as a reader, and as such I felt like it would be a good addition to my own Ghost blog.

Update: This guide is now obsolete, but I am leaving it up for reference.

As of Ghost 1.17.0 a new reading_time helper has been added (a feature I contributed to 🎉 (#9366, #9509, #9573)). I would advise using this helper over the method described in this article, as it is a lot simpler.

How does Medium determine this Read Time?

To implement my own version of the Medium Read Time, I first needed to know how Medium calculates this metric. Luckily, Medium has their own blog - hosted on Medium of course - on which they wrote an article on how this Read Time is actually calculated.

Read time is based on the average reading speed of an adult (roughly 275 WPM). We take the total word count of a post and translate it into minutes. Then, we add 12 seconds for each inline image. Boom, read time.
-- Medium

Adding it to a Ghost blog

Now that I knew how the Read Time is calculated, it was easy to actually implement it in my theme. I am currently using my own version of Ghost's Casper, so these steps will contain a few theme-specific steps, but I will try to explain how to amend them for different themes.

1. Word Count

After searching on the web, I found this lightweight library made specifically for word counting HTML pages. I found a CDN link for the library, which I used to add the library to my code:

<script src="https://cdnjs.cloudflare.com/ajax/libs/countable/3.0.0/Countable.min.js"></script>
<script>
$(document).ready(function () {
    Countable.count($(".post-full-content")[0], function(counter) {
        console.log(counter.words);
    });
});
</script>

Note: I use the .post-full-content class as a base for the word counter, because this is the default in my theme. You might need to change this to a different class depending on your theme.

2. Image Count

With the word count calculated, I still needed the image count for the calculation, which was even easier, as it didn't need anything besides basic JQuery:

var imageCount = $(".post-full-content").find("img").length;

3. Calculating the Read Time

With both the word count and image count, it was possible to calculate the Read Time, putting it together like this:

$(document).ready(function () {
    Countable.count($(".post-full-content")[0], function(counter) {
        var imageCount = $(".post-full-content").find("img").length;
        var seconds = counter.words / 275 * 60 + imageCount * 12;
        var minutes = Math.ceil(seconds / 60);
    });
});

4. Displaying the Read Time in the Post View

Only thing left now was actually displaying the Read Time at the top of every post. To do this, I created a /partials/read-time-calculator.hbs file in my theme, containing all the code from before, but with the addition of setting the content of a .read-time span to the number of minutes to read. This span can then be added anywhere you would like to display the read-time, but I added it at the top of my post next to the primary tag. The read-time-calculator partial should be added at the bottom of the default.hbs file, just before the closing body tag.

[partials/read-time-calculator.hbs]
<script src="https://cdnjs.cloudflare.com/ajax/libs/countable/3.0.0/Countable.min.js"></script>
<script>
$(document).ready(function () {
    Countable.count($(".post-full-content")[0], function(counter) {
        var imageCount = $(".post-full-content").find("img").length;
        var seconds = counter.words / 275 * 60 + imageCount * 12;
        var minutes = Math.ceil(seconds / 60);
        $(".read-time").html(minutes + " min read");
    });
});
</script>
[post.hbs]
...
<section class="post-full-meta">
    <time class="post-full-meta-date" datetime="{{date format="YYYY-MM-DD"}}">{{date format="D MMMM YYYY"}}</time>
    {{#primary_tag}}
        <span class="date-divider">/</span> <a href="{{url}}">{{name}}</a>
    {{/primary_tag}}
    <span class="date-divider">/</span> <span class="read-time"></span>
</section>
...
[default.hbs]
...
    {{ghost_foot}}
    {{> read-time-calculator}}
</body>
</html>

Note: Since I'm going to be putting it in the header of my blog posts, I added a '/' to fit in with the rest of the theme. Like I mentioned before, depending on your own theme, you can add the .read-time span wherever you see fit.

5. Displaying the Read Time on Post Cards

It's great that it's getting displayed at the top of the post view, but ideally, a reader wants to see the Read Time before opening a blog post. Which is why it still needs to be enabled for post cards - for example in the index view and the tag view. I did this by adding the following code to my post-card partial:

[partials/post-card.hbs]
...
<header class="post-card-header">
    <div class="post-card-tags">
        {{#if primary_tag}}
            <span>{{primary_tag.name}}</span>
        {{/if}}
        <span class="date-divider">/</span> <span class="read-time"></span>
    </div>
    <h2 class="post-card-title">{{title}}</h2>
</header>
<section class="post-full-content" style="display: none;">
    {{content}}
</section>
...

Besides the .read-time span, I added a hidden section for .post-full-content, so that the script would have the possibility to count the words for the full post, without actually displaying the full text.

If we stop here, however, only the read-time for the first post will be calculated, and will be applied to all posts, so I also edited the read-time-calculator partial.

[partials/read-time-calculator.hbs]
<script src="https://cdnjs.cloudflare.com/ajax/libs/countable/3.0.0/Countable.min.js"></script>
<script>
$(document).ready(function () {
    $(".read-time").each(function(index) {
        var postContent = $(".post-full-content").get(index);
        var readTime = this;

        Countable.count(postContent, function(counter) {
            var imageCount = $(postContent).find("img").length;
            var seconds = counter.words / 275 * 60 + imageCount * 12;
            var minutes = Math.ceil(seconds / 60);
            $(readTime).html(minutes + " min read");
        });
    })
});
</script>

The snippet above loops over all .read-time spans, gets the corresponding .post-full-content and calculates the Read Time for it, after which it is entered into the correct span on the post-card.

Conclusion

In this article, I provided a guide to displaying Medium Read Time in your own Ghost blog using a handlebars partial, and the Countable.js library. If you would like to see it in action, take a look around my blog to see it displayed in the various places (index, tags, post). If you would like to further inspect the code, I encourage you to check out my theme, and the read-time-calculator in particular.


If you enjoyed reading this article, please consider sharing it on Facebook, Twitter or LinkedIn. If you used this guide to add a read time indicator to your own site, or if you want to show what you did with it, leave a comment below!