The most native image gallery
Tags: webdev, javascript, css
Published: 2020-11-23 10:03:01

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](https://bundlephobia.com/result?p=native-gallery@0.0.4)). 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.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/143qmtn52nujwqag01oi.gif)

The gallery is named [`<native-gallery>`](https://github.com/korywka/native-gallery) to take advantage of [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_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:

```html
<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](https://web.dev/browser-level-image-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:

```css
/* -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](https://github.com/aFarkas/lazysizes) is supported as a polyfill.


## Magnetic behaviour 🧲

The magnetic behaviour is implemented using [Scroll Snap](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type) CSS property. Only a single image is visible after scroll:

```css
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:

```js
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:

```js
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`](https://github.com/korywka/native-gallery/blob/master/src/index.js#L14-L18) function for better performance.

Controls demo:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/w0pstm4furenrstw2fiu.gif)

## TL;DR 🏃‍♂️

* CSS property [scroll-snap-type](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type) for snapping
* CSS property [scroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior) for smooth JS scrolling
* `loading` attribute for [Native Lazy Loading](https://web.dev/native-lazy-loading/)
* Compatible with [lazysizes.js](https://github.com/aFarkas/lazysizes)
* [Custom Events](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events)
* It was named as `native-gallery` to become [Custom Element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) in the future
* Disadvantages: circular scrolling is [not implemented yet](https://github.com/korywka/native-gallery/pulls) 🤷‍♀️
* [630B gzip](https://bundlephobia.com/result?p=native-gallery@0.0.4)

[Code repository](https://github.com/korywka/native-gallery) / [Example](https://korywka.github.io/native-gallery/example/)

Any feedback is much appreciated ❤️