Lazy loading images and disqus in Hexo
I’d recently lookup up IntersectionObserver
for some other project and I thought to my self I wanted to make something with it. Lazy loading images was the first thing that got to my mind and that was something I wanted for my site as well. I’ve been working at getting my site as light as possible and I’ve wished I could defer loading of content below the fold earlier. I had found a hexo plugin for lazy loading images but it wasn’t enough for me.
Rendering
Before the image has loaded the placeholder is an gradient of the image that is loading. It’s generated by the npm package image-to-gradient (npm). However, it’s broken at the time of writing this. I’ve sent a pull request but you could use my git repo in the meanwhile. (Edit 2017-12-17: Pull request has been accepted and a new working version is up on npm.)
I was inspired by José M. Pérez’s post How to use SVG as a Placeholder, and Other Image Loading Techniques and I tried out node-potrace but I decided to go for gradients instead because they have smaller footprint and is simpler. The images should load fast so we won’t be seeing much of the placeholders anyways.
For the moment I haven’t made a plugin, instead I use a script directly under my custom theme. It hooks up to the after_render
of hexo and replaces img tags with some fancy html and then injects a script tag for loading of images as the user scrolls down.
hexo.extend.filter.register('after_render:html', imageLazyLoadProcess); |
First it uses regex to find all img instances.
var matches = htmlContent.match(/<img(\s[^>]*?)src\s*=\s*['\"]([^'\"]*?)['\"]([^>]*?)>/gi); |
Then for each match it gets the image stream and converts to a buffer from which size of image and corresponding gradient is calculated.
var imageStream = hexo.route.get(item.url); |
I’ve put some extra effort into how the image is displayed until loaded. The image placeholder should have the same size and ratio even when not loaded and independent of screen width. By using padding-top as result of height / width * 100
and height 0 (inspired from Andy Shora’s post Sizing Fluid Image Containers with a Little CSS Padding Hack) we can maintain ratio. I also needed to add a containing div and set the width so the placeholder can’t become larger than it should.
Each image item is replaced like this.
htmlContent = htmlContent.replace(regex, function (tag, pre, url, post) { |
Which will generate something like this.
<div class="img-container" style="width:647px;background:linear-gradient(rgba(98,159,207,1.0),rgba(248,248,248,1.0),rgba(249,249,249,1.0),rgba(246,246,246,1.0),rgba(246,246,246,1.0),rgba(249,248,248,1.0),rgba(252,249,249,1.0),rgba(246,245,245,1.0),rgba(249,249,249,1.0),rgba(220,232,243,1.0));"> |
And by using the following css we can show the placeholder correctly and with animation on load. To get a nicer effect I added opacity to the image and a transition which removes it when image has loaded. Se how src attribute is set further down.
.img-container { |
Even prepends with a <noscript>
-extra so it works without javascript with images anyway.
<noscript> |
Loading
The lazy loading script checks wether the image is visible and then triggers loading of image. The detection is made by IntersectionObserver
if supported else it falls back to getBoundingClientRect
and event listeners for scroll and resize.
function imageLazyLoad() { |
The loading of image is done by loading the source image url to a new Image
object before setting src on actual img tag so we can trigger some nice animations when the image has loaded rather than when setting src. The style for padding-top also needs to be removed.
function loadImage(element) { |
The script is triggerd at load.
document.addEventListener('DOMContentLoaded', imageLazyLoad, false); |
Demo
Lazy Disqus
I felt it was unnecessary to load comments directly not even knowing if the user would ever scroll down to the comment section. So I added a similar script for Disqus as well.
if (typeof IntersectionObserver !== 'undefined') { |
Embedding is simply done by adding a script tag for embed.js.
function embedDisqus(shortname) { |
You’ll need to call it like below to trigger it.
disqusLazyLoad('mollwe'); |
Summary
There you have it with loading images and disqus when in view, the full scripts mentioned in post can be downloaded here.