Optimizing image loads
Research studies have consistently shown that site speed is one of the most crucial factors that impact your engagement and conversion rates. Simply stated, the faster your website loads, the better your conversions, and therefore, your sales revenues. All else being equal, no other metric has a more direct and tangible impact on your bottom-line. On mobile devices, the constraints with smaller screen sizes and limited network bandwidths only serve to underline that concern even more. And given that the vast majority of your traffic is now on a mobile device, the negative impact of a site with poor loading speeds can be devastating.
Despite the fact that this is fairly well known, the reality is that a lot of e-commerce sites continue to be plagued by serious performance issues. Sites that have a lot of graphic content, such as graphic libraries, image catalogs or user generated graphic content, tend to have a significant load time, especially if there are several image assets that need to be loaded.. On one hand, images play a critical role in boosting user-engagement – a product photograph that’s well done can do wonders for conversion rates. On the other hand, populating a page with a lot of images can significantly impact the time it takes for the site to fully load and become responsive, and the resulting latency could potentially even result in some images not showing up at all, especially on mobile. In this blog post, we will study ways in which pages with heavy graphic content can be optimised in a way that provides the user a smooth experience without compromising on the quality or the composition of your pages.
To simulate what happens in a page with heavy visual content, I created a simple image gallery page with 100+ images and loaded up the page with a simulated 3G connection and a 4x performance slowdown to more accurately simulate a typical mobile device.
Sample image gallery hosted here
The result was a page with over 10 seconds of time-to-interactive, the whole site took 1.7 minutes to load and several images were broken. While the images were getting loaded, the page wasn’t responding to scroll and was stuck several times. Even after the nearly 2 minutes of load time, page scroll was jerky and slow.
Failed image requests on a mid-tier phone
Why does this happen
In order to illustrate this point with a real-world scenario that we encountered, I am going to deliberately take an extreme scenario because it helps uncover some very useful insights. One of our Shopify Plus customers has an extremely popular Wishlist module that gets plenty of love from their users – we are talking several thousand “Add to Wishlist” everyday. For this merchant, the rendering of the Wishlist UI presents a significant performance challenge because some of their users had a few hundred items in their wishlist. In those instances, it took over 2 minutes for the Wishlist UI to load sometimes, and rare as these instances were, it was still completely unacceptable. When we determined through a quick Lighthouse run, that the large number of images was the primary cause of the latency, we wanted to try and devise a robust solution that would address the performance issue but without negatively impacting the value of rendering those images given they were critical to driving engagement. We found that the problem happens mainly for 2 reasons –
- Browsers the way they are – For our wishlist page, we were loading the wish list products using a network call and then injecting the product tiles with the image tags into the DOM. If the wish list products are 100+, this triggers 100+ image GET requests initiated by the browser. With HTTP/1.x, browsers had a limit on the number of requests they can open at a time. This differed from browser to browser but was around 6 for most of them. Browsers have now adopted HTTP/2 which allows them to multiplex a connection for multiple requests on the same origin server. Connections can still timeout if some assets have to wait for a very long time or if the server is unable to set up a HTTP2 connection. We saw many of the image requests getting cancelled on a simulated Fast 3G connection.
- Image sizes – In pursuit of ensuring that the rendered images don’t look pixelated or blurred, often we end up loading images in resolutions much higher than necessary. Not only does this increase the time required to fetch these images from the server but also the time required to render each of these images increases. In the process of rendering these images, the browser consumes considerable amount of memory, resulting in a clunky and jerky end-user experience.
How to solve this
Depending on the use-case and the role of images in your user experience, there are solutions you can employ to control image load sequences more judicially, without negatively impacting the end-user experience.
Load attribute in the image tag
Chrome recently released support for “lazy” value in the “loading” attribute of an image tag. With this attribute value, Chrome automatically defers the load of the images that are currently not in the viewport and loads only the ones that are. For the images out of the viewport, Chrome tries to load it slightly before the user has scrolled to the image to ensure there are minimum wait times for the user. You can start using this approach by adding the loading attribute to your image tags as shown below –
<img src="img/demo.png" loading=lazy>
This can dramatically improve your load times and time-to-interactive and is absolutely the right thing to do if your use-case doesn’t expect the user to need all the images upfront. For example, a product collections page is a great example of a scenario when this approach makes sense – users take time going through the products and generally don’t need access to all the products upfront. One possible downside of this approach is that it’s only supported by Chrome at the moment and is not a part of Chrome’s stable releases yet. Check out the details of the release here.
Image Load Queue
We were facing the slow image loads on the shopper’s wishlist page, especially for shoppers who had north of a 100 wishlist items. We were considering going with the approach stated above – deferring image load until it comes in the viewport. However, there were 2 challenges –
A quick look at the device breakdown of the shoppers of this store told us that the store was getting accessed from a variety of devices and browsers, even Internet Explorer.
Users running into this problem were the store’s most engaged users since they had a wishlist with a large number of items. Being in the fashion vertical, wish lists are mostly used as an organisation tool for quick, handy access to products of interest. If the user is scrolling through the products fast, this can be as bad as loading all images together. We just tried out the “loading=lazy” image attribute with the Chrome experimental flag and while it works if the user works linearly through the page, it ends up opening all image requests if the user is looking for a product in their saved list and moves to-and-fro within the page. Therefore, it’s perhaps not the best idea to defer image loads until the product is in viewport for this use-case.
Server opening all image requests on load
We needed a way to load the images as fast as possible without opening more than a certain number of connections to the remote server. That way, the server would be able to serve a batch of images and only then proceed to the next page. We found this approach providing a much better user experience for this scenario because the page load itself wasn’t blocked/impacted.
To do this technically, we created a simple image-load queue that maintained the list of images getting loaded at any point in time and ensured that that list never exceeded a certain threshold. For our use-case, we limited it to 8. We then used image tag ‘onload’ attribute to notify once the image has loaded to pop that image from the load queue. Check out our implementation here.
Progressive Image Loading
If you have a very low resolution version available for each of your images, you can also do something that sites like WhatsApp and Medium too. This improves the user experience by progressively improving the image quality as its loaded.
The Medium blur effect for image loads
This can be used in combination with the image load queue solution to give the perception of near-instant load times. More details on the implementation can be found here.
As images become a more integral part of the store experience, it’s critical to keep in mind the state of affairs in the mobile context, where bandwidth and memory is still at a premium. While there are lots of interesting approaches of optimising image loads, the solution might depend on your use-case. Having a deep understanding of what the user is looking to accomplish helps build your image-load strategy. We would love to hear your what you do to handle images optimally.