Medium-like Image Loading with Vue.js (part 1)

 

When I’m stuck on a train or queuing at the supermarket I usually read one or two articles on Medium.

There’s plenty of stuff that I really love about Medium. Like the email they send me every morning. Or the personal recommendations on their main page.

And the blurry image loading. I mean, seriously, the first time I noticed it I was like:
jimesTooper

If you don’t know what I’m talking about, click on this link and see how the top image is displayed.

I recently started using Vue.js and I thought, well, let’s see if we can build a Vue.js component to do this!

How it works

So I wondered, how do this thing work? Fortunately José M. Perez has done a wonderful (Medium) blog post explaining the principle of this technique. He even provided us with a plain javascript implementation.

If you don’t want to read the whole article, the core principle is really simple:

  • Download a low-resolution image and scale it to the real size (your browser will take care of the blur)
  • Once your real image is downloaded, put it instead of the low-res one

Let’s start

For our example, imagine our HTML looks just like this:

<!DOCTYPE html>
<html>
<head>
  <style>
    img {
      width: 100%;
    }
  </style>
</head>
<body>
  <img
    src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg"
  ></img>
</body>
</html>

It’s very simple: it displays one image.

First let’s include Vue.js, our .js script and substitute the img tag by a custom one that will call our component, such as blurry-image-loader:

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
  <style>
    img {
      width: 100%;
    }
  </style>
</head>
<body>
  <blurry-image-loader
    src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg"
    small-src="https://cdn-images-1.medium.com/freeze/max/27/1*sg-uLNm73whmdOgKlrQdZA.jpeg?q=20"
  ></blurry-image-loader>
  <script src="vue-blurry-image-loader.js"></script>
</body>
</html>

Yay! Now that our HTML is ready, let’s create our script! First of all, we have to create a new Vue instance and to register our component:

Vue.component('blurry-image-loader', {})

new Vue({
  el: 'body'
})

Allright. As you probably saw earlier, our HTML component has two attributes: the url of the image in full size (src) and the smaller image (small-src). We can register those attributes as props in our Vue.js component:

Vue.component('blurry-image-loader', {
  props: [
    'src',
    'smallSrc'
  ]
})

N.B.: we have to use camelCase in javascript, so small-src becomes smallSrc.
Ok let’s go on step-by-step: let’s say the template of our component will be an image with the low-res image:

Vue.component('blurry-image-loader', {
  props: [
    'src',
    'smallSrc'
  ],
  template: '<img :src=smallSrc></img>'
})

So now if you open your HTML file in your browser, you should see a blurry image. But that’s not going to work with that template, we need the src attribute of the img element to change when our real image is loaded. So we need a kind of changing props, which is in fact a … data!

Let’s call it imageSrc and initialize it to the value of smallSrc:

Vue.component('blurry-image-loader', {
  props: [
    'src',
    'smallSrc'
  ],
  data: function () {
    return {
      imageSrc: this.smallSrc
    }
  },
  template: '<img :src=imageSrc></img>'
})

Good! We’re nearly there.
Now we need to load the real image when the component is created, and once this is done we have to change the value of imageSrc. Vue.js components have a ready attribute which gives us the possibility to execute a function once the component is ready. I believe this sounds like the right place to do our loading!

(N.B.: in Vue 2 the ready atribute is now called mounted)

(N.B.: as pointed out by Bokkeman in the comments, to prevent trouble with your browser cache set your image src attribute after the onload event handler)

Vue.component('blurry-image-loader', {
  props: [
    'src',
    'smallSrc'
  ],
  data: function () {
    return {
      imageSrc: this.smallSrc
    }
  },
  template: '<img :src=imageSrc></img>',
  // use mounted in Vue.js 2.0
  ready: function () {
    var img, that
    img = new Image()
    that = this
    img.onload = function(){
      that.imageSrc = that.src
    }

    img.src = this.src
  }
})

And voilà! We have achieved this with actually fewer lines of code than I thought!

You can check out my Codepen to see it live (I’ve added a small timeout to make sure you see the blurry image).

I you liked this article please share it and keep checking out this blog, I’m currently writing part 2 which will show how to smooth the unblurring!

 

To discover how to achieve the exact same effect with transitions head to part 2 over here.

 


You liked this article? You'd probably be a good match for our ever-growing tech team at Theodo.

Join Us

  • Hey Louis. Medium’s implementation goes a little beyond what’s covered in this article. This is a very simplistic implementation. I recommend looking at this article for more details on how to improve your solution even further.

    https://jmperezperez.com/medium-image-progressive-loading-placeholder/

  • Louis Zawadzki

    Hi Marek. Thanks for your comment! Yes indeed Medium’s implementation goes beyond this and in this article I just wanted to show the core idea.
    I’m actually writing a follow-up to this article to go further, so I’ll link it here once it will be live 😉

  • Still though, nice little trick and I appreciate Louis taking the time to share this.

  • Bokkeman

    There are plenty of gotchas with img.onload event handlers – one of which might catch you out is if the image is already in the browser cache. In possibly this and other cases the image might already be loaded before you attached your img.onload event handler. You should therefore set up your handler before you set the img.src property to prevent this.

  • Louis Zawadzki

    Thanks for pointing that out! I’ve updated the article :)

  • hey this does work but when on paginated results, the images are the same on page 2,3,4 as images from page 1
    how do i fix this ?

  • Louis Zawadzki

    Hi,
    The component appears to work with 2 images on the same page, like here: https://codepen.io/zkilo/pen/KNGzPo
    So I believe this is related to the way you’re doing the pagination.

    Could you provide more input on this? Like how you integrate your component inside your pagination?

  • Louis Zawadzki

    Hi Marek, I’ve finished written the follow-up to the article, you can find it here: https://www.theodo.fr/blog/2017/02/medium-like-image-loading-with-vue-js-part-2/