✨ Simple Svelte Responsive Image Gallery: Introduction #
We look at a simple Svelte responsive image gallery in this post. By simple, I mean to say the functionality is simple. Despite that, it lets us explore a couple of Svelte and Vite features which are a little more advanced. In particular, we look at glob importing where we can import, for example, all files matching a certain pattern into SvelteKit JavaScript code. We also use Svelte dimension bindings to make sure all images from our gallery — tall and wide — look good, maintaining aspect ratio as the window size changes. As well as being responsive, the images will be generated in Next-Gen formats. Finally, we add an optimization which should help with the Core Web Vitals Largest Contentful Paint metric. I should also mention we add lazy loading as another Core Web Vitals optimization.
Generally, I prefer to roll my own components whenever possible, rather than leaning on libraries, so really enjoyed putting this tutorial together. If you are looking for a simple scrolling gallery, supporting modern image formats that is responsive, this should do the trick. Even if you are looking for a fully-featured light box, you will probably find parts here which you can recycle for use with your own code or library.
⚙️ Getting Started #
There’s a bit to get through, so let’s get going! I have used a script to generate image data automatically to speed things up, so you will need to download those image data files as well as the images themselves in a moment. First, though, let’s spin up a new skeleton project:
From the options, choose Skeleton project, Use TypeScript:? No, Add ESLint...? Yes and Add Prettier...? Yes. As well as set up Svelte, we have installed a font and a Svelte component library to help with generating the responsive image boilerplate code. Together with those two packages, we have some icons for the next / previous buttons to move between images. Finally, there are a couple of packages to help with lazy loading and Next-Gen image generation and caching.
As an extra bit of setup, update vite.config.js
for use with vite-imagetools
:
File Download #
Lastly, create a src/lib/assets/
folder and download the six images from that location in the Git repo . Finally, create src/lib/generated
and repeat, copying the JavaScript files from the equivalent folder on the GitHub repo . Typically, you would want to generate these files in a script, updating for required images
formats, widths and pixel densities, though I have done this already to save time. You can take a look at the script which generates this data including low resolution placeholders in the repo.
🗳 Poll #
🔨 Server Route #
Next we will create a server route. This file will look for the JavaScript image data files we
just downloaded and generate a single array image data (spanning all images). Create the file at src/routes/+page.js
and add the following content:
There are one or two interesting things in here. In line 3
, we are
importing all the JavaScript files in the lib/generated
folder. To
do this, we use a Vite Glob Import . Essentially, Vite expands this to an object:
Each of the members of the object is a key-value pair, with the key being the path for one of the
files in our folder. The value in each case is the import function, so to complete the import, we
need to call the function on each field. We do that in line 4
,
generating a promise for each file we import and mapping all the promises to an array.
Over the following lines, we extract the default export from each of the files using the Promises API . If this is your first time using async/await
, you might find the explanation in the post on the SvelteKit Image plugin useful.
Our endpoint generates an array of image data which we will use next on the home page.
🏠 Home Page Svelte #
Next, we will replace the code in src/routes/+page.svelte
with the
following:
The onMount
function is called when our home page is created. We initialize
our lazy load at this point. You can see more on this in the post on Lazy loading iframes in SvelteKit.
Lines 18
– 21
probably
seem pointless, as we do not use the result anywhere. In these lines, we are importing the files we
use in the endpoint to generate the image data array. In fact, we only do this import here to ensure
the images are cached. You might find you can omit this code running in dev mode, but switch to preview
(after building) and have no images!
In line 34
we add our image gallery component to the DOM. Let’s
add some global CSS, then look at the gallery component code and a couple of ancillary components to
our project next.
Global CSS #
Create a new src/lib.styles
folder. Inside, add global.css
with the following content:
🧩 Simple Svelte Responsive Image Gallery Components #
We will use feather icons via Iconify for our forward and previous user interface buttons. Create a folder at src/lib/components
then add NextIcon.svelte
and PreviousIcon.svelte
to the folder, and paste in this code:
src/lib/components/NextIcon.svelte
— click to expand code.
src/lib/components/PreviousIcon.svelte
— click to expand code.
We’re almost done now! Next step is to add the final missing piece; the gallery component.
🖼 Ribbon Gallery Component #
The image gallery will have a few features to make the pictures look their best. This includes preserving the image aspect ratio when the window is resized and keeping all images the same height as we scale. As well as that, we want to ensure that for a small-screened device, the widest image in the gallery can be displayed, without panning. For this to happen, we need to work out which is the widest image and use its aspect ratio to set the height for all the images. To get all of this right, we will use Svelte dimension binding. There is a little maths (math) involved, but it’s not too complex.
Let’s start putting the image component together. Create a src/lib/components/RibbonGallery.svelte
file and paste in the following code:
Here in lines 10
& 11
we create variables which we need to hold the measurements
for our container height and width. Then at lines 15
to 22
we have a utility
function to work out the image with the highest aspect ratio. Aspect ratio is width divided by height,
so the widest image has the largest aspect ratio.
Image Height #
Next in lines 24
– 32
512px
. In a moment, we will see that we bind containerHeight
and containerWidth
to the actual DOM object dimensions. Because of that, we need to wait for the DOM to be ready, before
we have a value (hence the guard in line 27
). The element we measure will have the
images on top and some controls to shuffle through the images below. In between, there might be some
space, depending on the browser window height. We always want to allow some space for the controls
below, so in determining in the height for our images, we subtract the height of the controls (59px
) in line 28
.
Moving on to the code in line 30
. Let’s call the difference between the height
of our measured element and the height of the controls the maximum height. Generally, we want the
images to be as big as possible, so try to set their height to be equal to the maximum height. In
line 30
, we look at the widest image and if we find it is just too wide to
display at maximum height (without having to pan), we reduced the height of all the images. The
height we choose is back calculated from the width of our element and the aspect ratio of this
widest image.
So, this block is just working out when we need to reduce the image height, and what that reduced
height should be. We call the calculateHeight
function when the component first mounts
(line 37
) and then again when it updates (line 42
), to keep the height
good.
Previous, Next Image Logic #
Let’s add some logic to move between images next, by pasting this code at the bottom of the same file:
In lines 53
and 57
we are using the
modulus operation (%
) so we can loop around to the first or last
image when we get to the last image. I really love the way Svelte handles animation and makes it
easy to add some polish to image transitions in image galleries. Here though in-built HTML
functionality is pretty good and we will rely on that. In particular, we are using element.scrollIntoView()
.
For this API to work, we add a unique ID to each of our images and scroll to the id
of whichever image we choose. The rest just works! If you have a lot of images though and scroll
from the first to last, scrolling can be quite quick when smooth scrolling in switched on! If the user
prefers reduced motion, we revert to auto
which scrolls a little slower.
That’s all the JavaScript, so let’s add some HTML markup now.
Svelte Dimension Binding #
Paste this svelte code at the bottom of the same file:
We saw previously we had dimensions of the container element in the JavaScript for this component.
In line 91
you see how we bind the Svelte measured dimension to the
JavaScript variable. Once more, Svelte makes something that could be very complicated easier. Be careful
not to use this where it is not necessary, as it comes with a performance hit. Learn more about Svelte dimension bindings in Svelte docs .
Image Load Optimization #
We have a few image loading optimizations here to help improve Core Web Vitals together with the user experience as well as SEO of your app. We already mentioned images are lazy loaded. This means the user’s
browser initially only loads the images that are in view. The others are only loaded when the user
scrolls over. The vanilla-lazyload
plugin helps with this. On top,
we give a hint to the browser in line 104
to load images lazily. We
want the user to see something when the page first loads, so the first image loads eagerly.
Next, we add low resolution placeholders. Together with width and height data, which we supply, this lets the browser know how much space to reserve for the images, reducing cumulative layout shift. Because we want the image to scale to the browser width and maintain aspect ratio, there is some potential for CLS for any elements below the images in the DOM. Bear this is mind if you use this code for other projects.
Finally, we set importance
to high for the first image in line 105
. This is another hint to the
browser to give the user something to see quicker and should help to improve the First Contentful
Paint metric.
As an aside, in line 95
we add a unique ID to each image to help with
the scroll into view function we looked at earlier.
Style #
The last part is to add style. Unlike some other tutorials on this site, styling is needed here
for the gallery to work as expected. This is mostly because we set heights on some elements. To
finish off, paste this CSS code at the end of the RibonGallery.svelte
file:
That’s all the code and everything should work now. Give it a try!
💯 Simple Svelte Responsive Image Gallery: Testing #
That’s it, mission complete (apart from testing). First, we want to make sure the controls work for moving between images. Make sure you can bring all the images into view using the previous and next buttons. Then try resizing the browser window. All images should maintain aspect ratio as you make the window larger or smaller.
The final test is to make the browser window tall and narrow and scroll to the fourth image. It should span the width of the window. You should not need to pan to see the entire image.
If that’s all working, let’s recap and look at some extensions.
🙌🏽 Simple Svelte Responsive Image Gallery: What we Learned #
In this post, we saw:
- how you can bind the dimensions of an element to a JavaScript variable in Svelte;
- a way to import all the files in a particular using Vite glob imports; and
- how to optimize images for Core Web Vitals and better user experience.
I do hope there is at least one thing in this article which you can use in your work or a side project. As an extension, you might consider infinitely looping the images, so you don't get the disjoint scroll when you reach the last image. You would have to anticipate reaching the last image and tack the first image onto the end of the array (and something similar for scrolling backwards past the first image).
You can see the full code for this using Simple Svelte Responsive Image Gallery tutorial on the Rodney Lab Git Hub repo . As always, get in touch with feedback if I have missed a trick somewhere!
🙏🏽 Simple Svelte Responsive Image Gallery: Feedback #
Have you found the post useful? Do you have your own methods for solving this problem? Let me know your solution. Would you like to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram . Also, see further ways to get in touch with Rodney Lab. I post regularly on SvelteKit as well as other topics. Also, subscribe to the newsletter to keep up-to-date with our latest projects.