5 ms·
Ask HN: How to display a large number of images speedily?
I'm trying to load about 700 to 1000 images on a page. I'm a newbie when it comes to front-end dev. Here's what I've done so far:
* Optimized the images (~250 kb/image)
* created thumbnail versions (& load full versions in a modal)
* added lazy loading
It's deployed on netlify, but it's still not quite snappy.
If possible, I'd like to avoid pagination.
What else can be done to load images faster?
- jepler 9y agoAre you lazy-loading your (thumbnail) images? this js library seems to be highly recommended by its author: https://github.com/ApoorvSaxena/lozad.js https://github.com/ApoorvSaxena/lozad.js but I don't have any experience with it. If users typically view the full images sequentially, then a lightbox that preloads the next image(s) may help with perceived performance.
- dhruvkar 9y agothanks, I'll check out that library. I'm using blazy (https://github.com/dinbror/blazy https://github.com/dinbror/blazy) right now.
- PaulHoule 9y agoThe first problem you have is that this is a lot of data. You are talking 250 MB or so worth of images. If you use JPEG, you can get images very small at the cost of image quality. There are also tools like PNG Gauntlet that will reduce PNG sizes; one thing that tool will not do is convert an RGB image to a limited palette and then store it as indexed. Sometimes that works wonders. Both Google and Microsoft have image formats that compress better than PNG, but they don't support each other's formats. In your case I would consider some kind of "infinite scroll" gimmick like Twitter. Maybe you load the first 5 images and as the user scrolls down you load more.
- dhruvkar 9y agoyeah, that's what the lazy load plugin is doing right now. does that mean pngs are preferable to jpegs?
- arkitaip 9y agoAll those http requests cost so maybe combine the thumbnails into a few sprites consisting of 64x64 graphics?
- dhruvkar 9y agowould you combine, then split it in the browser? is there an example? interesting, I've never heard of this approach.
- wanda 9y agoLazyloading is good, I've written a pretty speedy lazyloader myself that also works for CSS background images, but serving up lo-fi thumbnails when many images are displayed at once is something I tend to do regardless. Lazyloading often works by checking if an image element is in the viewport — if so, the src attribute is added dynamically and the image is loaded as soon as this is complete. However, if the user scrolls quickly or has a large screen, lots of images may be loaded simultaneously — and if those images are all fairly large, this may cause some performance problems. You could try using requestAnimationFrame to load images one-by-one, but at the fastest possible rate, i.e. to stagger the loading of images. You could do this by listening for the 'load' event on each image in order to trigger the loading of the next image. Assuming you store the image URL in a data attribute like 'data-src', a simple, vanilla implementation of what I just described might look like this: /* In production, we would throttle or debounce this scroll handler, but for brevity we'll just attach it directly: */ window.addEventListener('scroll', function () { var all_imgs = document.querySelectorAll('img[data-src]'); // [].slice.call to convert qSA's NodeList to an Array var visible_imgs = [].slice.call(all_imgs).filter(function (img, index, arr) { /* your logic for determining if the image is in the viewport would go here */ }); return loadImages(visible_imgs); }, false); /* This function will work by being passed an array of images that were in the viewport at the time of the scroll event triggering, and will call itself recursively until all of those images are loaded. */ function loadImages (imgs) { if (imgs.length < 1) { return; } return requestAnimationFrame(function () { imgs[0].src = imgs[0].getAttribute('data-src'); // remove the data attribute so that the image isn't loaded over and over again imgs[0].removeAttribute('data-src'); imgs[0].addEventListener('load', function () { return loadImages(imgs.slice(1)); }, false); imgs[0].addEventListener('error', function () { return loadImages(imgs.slice(1)); }, false); }); } NB: I have not really tested this approach or the code above, I'm shooting from the hip here because I'm in transit right now — but if it does work, it might help improve perceived performance. Or it might make everything look even slower in which case I'm an idiot.