Hi, I want to share with you an approach to create the most native web gallery. By native, I mean a lot of work is done by the browser itself with minimum code (630B gzip). The gallery relies on some of the features that are supported by the latest versions of browsers (excluding experimental ones). It also corresponds to the principle of graceful degradation: to work in older browsers but lose some functionality.
The gallery is named <native-gallery>
to take advantage of Custom Elements in the future. For now, it is just a custom HTML tag.
There are no strict requirements for HTML markup, so let's keep it as simple as possible:
<native-gallery>
<img src="1.jpg" width="1600" height="900" alt="">
<img src="2.jpg" width="675" height="900" alt="" loading="lazy">
<img src="3.jpg" width="1600" height="900" alt="" loading="lazy">
<img src="4.jpg" width="1600" height="900" alt="" loading="lazy">
<img src="5.jpg" width="1600" height="900" alt="" loading="lazy">
</native-gallery>
Lazy loading 🚀
The first modern feature used here is Native lazy loading with loading="lazy"
attribute. There was some strange behaviour for a horizontal container (not window
) scroll that all images were loading at the beginning anyway. I got around this by hiding and showing images back after page renders with these few lines:
/* -loaded class is set at initialization */
native-gallery:not(.-loaded) [loading="lazy"] {
display: none;
}
So the loading
attribute is omitted in the first image tag for rendering it before the initialization of the plugin.
Since lazy loading is an important feature for production usage, lazysizes.js is supported as a polyfill.
Magnetic behaviour 🧲
The magnetic behaviour is implemented using Scroll Snap CSS property. Only a single image is visible after scroll:
native-gallery {
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
native-gallery img {
scroll-snap-align: center;
}
Mixed sizes images are supported too 🧙♂️
Any browser that does not support this feature will scroll the gallery with the standard behaviour.
Controls and events ⌨️
Controls and events are almost the only reason why this gallery contains javascript code. I tried to keep them as simple as possible. To navigate to the next image we need to preload it first:
function preloadImage(image) {
if (image.complete) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
image.setAttribute('loading', 'eager');
image.addEventListener('load', () => resolve());
image.addEventListener('error', () => reject(Error(`can't load image: ${image.src}`)));
});
}
And then just to scroll container to the new position:
preloadImage(nextImage)
.then(() => {
root.scrollTo(root.offsetWidth * toIndex, 0);
})
.catch(error => console.error(error));
To listen to change events, we subscribe to the container's scroll
event through the throttle
function for better performance.
Controls demo:
TL;DR 🏃♂️
- CSS property scroll-snap-type for snapping
- CSS property scroll-behavior for smooth JS scrolling
-
loading
attribute for Native Lazy Loading - Compatible with lazysizes.js
- Custom Events
- It was named as
native-gallery
to become Custom Element in the future - Disadvantages: circular scrolling is not implemented yet 🤷♀️
- 630B gzip
Any feedback is much appreciated ❤️
Top comments (0)