Hackathon: LQIP

Tim Sielemann
Haiilo
Published in
3 min readJun 23, 2020

Today’s communication heavily relies on pictures. You can see it on every social network basically. For some it’s even the main thing (Looking at you Instagram!). Same goes for COYO. You can add images everywhere: timeline, comments, messaging, blog articles etc. This leads to some big data chunks which have to be downloaded to the user’s device and until then there is a loading spinner or even worse white areas or even worse some jumping UI after the image has been loaded. UGLY! Thats why we need low quality image previews (short LQIP) until the real image is loaded and the Hackathon was a perfect opportunity for me and some other frontend/mobile affine people at COYO to realize that.

LQIP image example

First of all what is the status quo. I will take an image attached to a timeline item as an example in this article. A timeline item in COYO is really similar to e.g. a Facebook post. Something you write that appears on the timeline of other people that are following you. Right now we add the links to the attached images of a timeline item in the response body of the server response which is called to get the timeline and then show them inside an image html element. So far so good. But if you are on a slow internet connection it looks like this:

No LQIP

That could be more beautiful and therefor we came up with the idea of LQIP. We didn’t want to have additional calls to the API to get the low quality placeholder because that would also create that behaviour only for a shorter time period. So what we did is adding a downscaled image version with only 10px width as base64 encoded string to the payload of the timeline item to show the LQIP immediately. We also added the original image size to the payload so we can calculate the ratio of the image to stretch the LQIP version. The resulting code is quite simple and small:

We have two image elements, one containing the LQIP and one containing the original image. On the load callback of the original image, which is called after the image is downloaded completely and ready to be shown we make it visible. As the images should always take 100% of their container we can simply calculate the bottom padding of the wrapper with the ratio of the image. That will lead to a non-jumping UI after the image is rendered. As you see we also implemented some default LQIP which is needed for legacy images that does not contain any LQIP information and some short timeframe while the LQIP is calculated as that is done asynchronously in the backend after the upload of the image. The result already looks quite promising:

LQIP no filter

As you see the LQIP is rendered immediately after the skeletons are gone. But there is still room for improvement. To make it even more beautiful we added some SVG filter for blurring the preview:

To boost frontend performance we added an IntersectionObserver to the image component that will only load the original image if the image is inside the users viewport.

We observe the components element and use the subject isVisible$ to start downloading the image only if its wrapping element is coming into the viewport. The intersection observer is not available in all Browsers (IE you suck!) so we default to load all images right after creation.

Summed up this is a small code change for a real benefit when it comes to user experience and frontend performance. This is what the final result looks like:

LQIP with filter and IntersectionObserver

--

--