One of the solutions for improving frontend performance that I shared at Meet Magento Croatia 2017 was a workaround whose aim is to load and render important product images very fast. The underlying idea is to base64 a lower quality version of the important images and use data URI to embed them inside the HTML document. When I say important images, I mean images that are in the product list/grid and are usually interesting to a visitor and shown above the fold. This approach is not new and it’s been used for a long time on the Amazon ecommerce website.
I created a Magento 1 module around this idea and promised at my presentation to share it with the community. You can download/clone the module here: https://github.com/revampix/Revampix_Base62LazyImages
I’ve also used the following vanilla JS library for lazy loading of images: https://github.com/verlok/lazyload
This is a quick demo that shows what the final result looks like. For the demo I simulated a slow 3G network:
You can also see it in action here: https://www.darvart.de/holzfliegen.html
In the demo, you will notice that the first 2 product images are shown immediately when the new page is initially rendered on the screen and then the rest of the images are loaded by lazy loading. Also, if you look at the video in full screen mode, you will notice that when the first 2 product images are initially shown, they are a low quality, but when the lazy loading starts loading the rest of the images, also the first 2 images are also loaded in better quality. The idea behind this behaviour is that we like to show something meaningful to the user as soon as possible and even if the images are low quality, the user will recognise what is on the screen.
Let’s get more technical:
Here are some technical details that explain the concept:
- I don’t embed all the images but only those that are above the fold or close to above the fold. I can configure how many images I want to embed. The idea is that I don’t want to increase the size of the HTML document too much.
- For less important images, I embed a transparent gif and later, when the user scrolls down the page, I lazy load the original image.
- When I do lazy loading, I fetch images that are appropriate for the users screen/device. Thanks the to PICTURE tag and to a good JS library for lazy loading.
- When I generate the base64 version of the important images, I save them in Magento cache in order to minimise file reads for the following requests. This means that in the current version of the module you may need to clean the Magento block cache in case you upload a new product image. Of course this could be fixed in future releases of Revampix_Base62LazyImages extension.
- I directly insert the JS of the lazy loading library near the body end. I didn’t find it necessary to include the JS from an external file, because the script size is 1.81 KB gzipped and I wanted to keep the module very simple.
The Magento module allows you to specify how many images to embed as base64 inside the HTML document. It also allows you to specify the quality of the images that will be generated.
This is what the admin interface of the module looks like so far:
The screenshot above means:
- The functionality for lazy loading is enabled. If not enabled, we may try to fallback to the standard way how Magento displays product images in a grid/list. I show this in the code snippet a paragraph below.
- The quality of base64 images will be 25%.
- We will have a maximum of 4 base64 images per HTTP request.
You also will be able to specify different image sizes for different screen sizes. I didn’t make this configurable by module configuration because the module is still experimental and I think that the basic functionality I have created so far is sufficient. In order to have different images for different screen sizes you should take a look at the documentation of PICTURE tag.
You can specify image sizes by following this example:
<a href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $this->stripTags($this->getImageLabel($_product, 'small_image'), null, true) ?>" class="product-image"> <?php // Code we need to inject in templates in order to use /** @var Revampix_Base62LazyImages_Helper_Data $lazyLoadHelper */ $lazyLoadHelper = Mage::helper('revampix_base62lazyimages'); if ($lazyLoadHelper->isEnabled()) : $pictureSourcesConfig = array( array( 'resize' => '136', 'media' => '(max-width: 360px)', ), array( 'resize' => '180', 'media' => '(max-width: 640px)', ) ); echo $lazyLoadHelper->getBase62LazyImages($_product, 280, 220, $pictureSourcesConfig); // If the module Revampix_Base62LazyImages is is disabled we fallback to default RWD IMG tag else : ?> <?php $_imgSize = 210; ?> <img id="product-collection-image-<?php echo $_product->getId(); ?>" src="<?php echo $this->helper('catalog/image')->init($_product, 'small_image')->resize($_imgSize); ?>" alt="<?php echo $this->stripTags($this->getImageLabel($_product, 'small_image'), null, true) ?>" /> <?php endif; ?> </a>
In the example above, we pass the following parameters to the getBase62LazyImages() function:
- $_product – Mage_Catalog_Model_Product
- 280 – int, image width
- 220 – int,image height
- pictureSourcesConfig – array, configuration for different image sizes that we would like to load for different screen sizes.
This is what the generated HTML of the getBase62LazyImages() looks like:
<picture> <source media="(max-width: 360px)" data-original-set="/136x.jpg" /> <source media="(max-width: 640px)" data-original-set="/180x.jpg" /> <img id="product-collection-image-3" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAY..............." data-original="280x.jpg" alt="Example alt text" /> </picture>
I have some further ideas:
I might implement something that would allow me to pre-warm the images cache because running the module directly in production may slow down people’s webshops. May be I will use a special cookie and if the module recognises the cookie, I will then perform a resize. Otherwise, if there is no cookie, the module will fallback to the default behaviour (no lazy loading and using IMG tag).
I also might postpone the lazy loading of the images that are in base64 because I’ve noticed that sometimes the lazy load library tries to lazy-load those images too quickly and the side effect is that I don’t see them while the better quality versions are being loaded.
Well, that’s it so far. I am still playing with this technique and I am sure that there is a lot more to improve. I am confident that this will be helpful for somebody out there because it will help you show your images faster and save some data for your customers (thanks to the PICTURE tag and the lazy load JS library).