You may have noticed that certain websites will load a low-quality image first, and when the full resolution image is ready it will replace the low-quality image with the full-size image. Here on Medium you can notice this effect in place. The low quality images are simply blurred.

Medium’s progressive image loading example

Getting Started

For my project I use Amazon s3 for storing images and graphics magick (http://www.graphicsmagick.org/) for resizing images. When a user uploads a photo, I upload the full size photo to a ‘temp’ bucket, and then trigger the backend to download the file from the temp bucket, and use graphics magick to split the image into a small, medium, and large size.

The reason I upload to a temporary bucket and then download it on the backend is that graphics magick can only run on the backend, and images are, more often than not, too large to pass through http without having to do multipart http POST, and without going into too much detail for the project I was working on it was easier/more efficient to upload from the front end. You can see how I did this here, but know that it might not work as is for your project.

If you don’t understand Promises that well or promise.all do not worry. line 45 takes the sizes array ([50, 200, 500]), and assigns each size in the array a new process function with the size as the parameter. So, the variable actions essentially becomes: [process(50), process(200), process(500)] and line 46 takes that array and makes it an executable promise that doesn’t run then() until each of the promises in the array are executed.

The only real important section of the above code is line 21, which strips the file extension for the image name and replaces it with “_[size].jpg” ie image.png => image_50.jpg, image_200.jpg, image_800.jpg. This is needed for the image loader to be able to fetch the correct size.

finish by adding the image locations to your database.

post: {

id: 1,

body: 'lorem ipsum',

image: {

url: {

sm: s3BucketUrl/image_50.jpg,

md: s3BucketUrl/image_200.jpg,

lg: s3BucketUrl/image_800.jpg

}

}

}

The Image Loader Directive

If you don’t understand how Angular Directives work I would recommend reading this first.

Let’s dissect the directive.

line 18 defines an image source if the image it is trying to load, fails. While the next line (19) tells the loader what size image to try to load. Next we have an event emitter to tell the parent component that there was an error loading the image.

here is an example of how the directive is used:

<img

src="{{profileImage?.url?.sm}}"

image-loader

[imgSize]="lg"

[fallback]="/assets/fallback-image.jpg"

(emitOnError)="handleImageError()"

/>

onLoad()

Back to the directive. Line 35–41 attaches listeners to the image onload and onerror functions to allow us to run code when either of those events happen. and gives us functions to stop listeners when we are done loading the image.

NOTE: NEVER USE (onload) or (onerror) inside your html template in your img tags. This opens your app up to possible XSS attacks! :’(

Let’s take a look at our custom onLoad function at line 63. First we stop the app from listening for onload events. Line 65 we split the src string at each “_” and assign it to a new array. We then pop the last item in the array which would be “_50.jpg” since that was the small image size. And join the array back together putting back any “_” that were removed by the split function. Finally we run loadLargeImage().

loadLargeImage()

First, on line 44 we add the large image suffix and extension. In this case it is “_800.jpg”. We define a new Image and call it largeImage, give it the source which should be the url to the large image we created, and wait for it to finish loading.

When it is finished loading we use Angular’s Renderer2 to replace the native element’s (the img element in which the directive was placed on) source to the large Image source. And voilá! It works!

onError()

If the image fails for whatever reason. The directive will replace the native element src with the fallback image src with the onError function.

ngOnDestroy()

removes event listeners when directive is no longer in use.

Conclusion

Progressive image loaders allow users to see a lower quality image while your website loads the larger image in the background, and handles image errors without showing ugly broken image links.

You can take this tutorial one step further and blur your smaller size images with the html Canvas or graphics magick like Medium.

Thanks for Reading! ❤

— Lukas