✨ Simple Svelte Responsive Image Gallery: Introduction
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 responsive image boiler plate. Together with those two packages, we have some icons for the next / previous buttons to move between images. Finally there's a couple of packages to help with lazy loading and Next-Gen image generation and caching.
As an extra bit of setup, update
svelte.config.js for use with
Lastly create a
src/lib/assets/ folder and download the six images from that location in the Git repo . Finally create
🔨 Server Route
src/routes/index.json.js and add the following content:
There are one or two interesting things in here. In line
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/index.svelte with the following:
15 we have a standard SvelteKit load function in which we get the image data array from our endpoint.
onMount function is called when our home page is created. We initialise our lazyload at this point. You can see more on this in the post on Lazy loading iframes in SvelteKit.
35 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 build and have no images!
49 we add our image gallery component to the DOM. Let's add the code for this and a couple of ancillary components to our project next.
🧩 Simple Svelte Responsive Image Gallery Components
We will use feather icons for our forward and previous user interface buttons. Create a folder at
src/lib/components then add
PreviousIcon.svelte to the folder, and paste in this 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 of 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.
Lets start putting the image component together. Create a
src/lib/components/RibbonGallery.svelte file and paste in the following code:
Here in lines
11 we create variables which we need to hold the measurements for our container height and width. Then at lines
22 we have a utility function to work out the image with highest aspect ratio. Aspect ratio is width divided by height, so the widest image has the largest aspect ratio.
Next in line
32 we work out what height our images should have. To start the ball rolling, we set an initial height of
512px. In a moment we will see that we bind
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
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:
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
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
Svelte Dimension Binding
Paste this svelte code at the bottom of the same file:
Image Load Optimisation
We have a few image loading optimisations 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 load 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.
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
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 work let's recap and look at some extensions.
🙌🏽 Simple Svelte Responsive Image Gallery: What we Learned
In this post we saw:
a way to import all the files in a particular using Vite glob imports,
how to optimise 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.