LQIP in Rails using Thumbor and base64

Use Thumbor and base64 to create tiny image thumbnails for your Ruby on Rails applications.


Sun Jan 31 2021 - 4 min read
ruby-on-railsself-hosting

There are many ways to do low quality image placeholders for applications. This is a simple way using Thumbor and Ruby to generate blurry base64 thumbnails. This way the image can be loaded in the first HTML load and there is no need for additional requests to fetch the  thumbnails while the original images are loading.

You will need Thumbor to use this method. If you're unfamiliar with Thumbor then I wrote an introduction here.

The idea is to take the original image and then drop the quality as low we can and then make it small with a blur effect.

Original image. About 400kB

Let's start by applying the quality filter and drop the quality to say 5

282kB

Next, we take the image size down to say 100 pixels wide and keep the aspect ratio.

1.5kB

Now, if we blow this image up to a reasonable size it will look real bad. So what I like to do is play with the quality and size a bit and add a bit of blur. In this example I changed the size to 200 pixels wide, the quality to 25 and added 5 blur:

1.9kB

The size is a bit larger now but I think the trade off is worth it. Here is the URL for the final image if you want to have a look at the settings:

https://thumbs.mskog.com/200x/filters:format:(jpeg):quality(25):blur(5)/https://images.pexels.com/photos/5733127/pexels-photo-5733127.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260

We can now use this excellent Base64 Image Encoder site to convert the image to base64. The final image will thus be about 3.26kB. Keep in mind that this is text and can be compressed. Gzip compression should be able to take this down to about 2kB once we add it to a website.

Ok, so now we have settings that we like. Next up we need to write some Ruby code to convert the image to base64. This is very easy to do:

require "net/http"

url = "https://thumbs.mskog.com/200x/filters:format:(jpeg):quality(25):blur(5)/https://images.pexels.com/photos/5733127/pexels-photo-5733127.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260"

base64 = Base64.encode64(Net::HTTP.get(URI(url))).gsub("\n","")

That is all you need. We now have a base64 string representation of the image. I like to save this result to a database field on the model with the image. You can also use the Rails cache and generate these base64 images on the fly if you want. This will however be very slow and that is probably not what you want. Instead, just use ActiveJob to create these in the background once the original image URL is saved. Then save them to the model. Another idea is to use Active Storage and hook into the metadata analysis to add your base64 strings this way.

Right, time to put the base64 images to use. What you want to do is to use these as placeholders while the full image is loading. I usually use vanilla-lazyload for this. It is a simple and tiny project that gets the job done. Do mind that if you use Turbolinks you have to make sure that vanilla-lazyload does its thing after Turbolinks is done. If you use Webpacker it will look something like this in your application.js file:

import LazyLoad from "vanilla-lazyload";
const lazyLoad = new LazyLoad();
document.addEventListener("turbolinks:load", function () {
  lazyLoad.update();
});

Finally, let's use the base64 placeholder in the template:

<img class="lazy" data-src="YOUR_FULL_IMAGE_URL>" src="data:image/jpeg;base64,<YOUR_BASE64_STRING>"/>

And there we go. Lazy loaded base64 placeholders that will be replaced by the full image once it is done loading. If you want to see how this looks in practice then I made a test site for it.