Implement an Image Carousel in Svelte

Milecia

Svelte is another popular JavaScript framework for building front-end apps. It's been around since 2016, similar to Angular, although it has a much smaller community and fewer packages available. Despite this, there are still thousands of developers that love building apps with Svelte compared to the other frameworks.

That's why we're going to build a common component so that you can easily apply it to your own apps. In this post, we're going to build an image carousel that fetches images from Cloudinary and lets users click through them in our app.

Set up project

If you don't already have a free Cloudinary account, you should go sign up for one at this point so you can pull your own images to work with in the carousel. Now we'll set up a new Svelte app using the following command.

1$ npm init vite image-carousel --template svelte

You'll be prompted in your terminal to select a few options. Make sure you select the svelte framework and the svelte-ts variant. Then you'll need to go to the new image-carousel directory and install the dependencies.

1$ cd image-carousel
2$ npm i

Now that we have the Svelte app bootstrapped, let's dive in and add a new component to the project.

Create the carousel

Svelte components are built completely different from the other frameworks so the syntax might look weird at first. Instead of making functions and classes like with the other frameworks, you have .svelte files that have <script>, <style>, and regular HTML tags. It's pretty easy to pick up as a framework, especially if you are relatively new to front-end development because it only adds a few extra things to make the components responsive.

Let's add a new file to src > lib called Carousel.svelte. This will hold all of the logic we need to request our images from Cloudinary and render them on the page with a little transition between images when a user clicks a button. In this new file, add the following code.

1<!-- Carousel.svelte -->
2<script lang="ts">
3 import { slide } from "svelte/transition";
4 import { onMount } from "svelte";
5
6 let images = [];
7
8 let currentSlideItem = 0;
9
10 const nextImage = () => {
11 currentSlideItem = (currentSlideItem + 1) % images.length;
12 };
13
14 const prevImage = () => {
15 if (currentSlideItem != 0) {
16 currentSlideItem = (currentSlideItem - 1) % images.length;
17 } else {
18 currentSlideItem = images.length - 1;
19 }
20 };
21</script>
22
23{#if images.length === 0}
24 <div>No images to show</div>
25{:else}
26 <div class="carousel-buttons">
27 <button on:click={() => prevImage()}>Previous</button>
28 <button on:click={() => nextImage()}>Next</button>
29 </div>
30 {#each [images[currentSlideItem]] as item (currentSlideItem)}
31 <img
32 transition:slide
33 src={item.url}
34 alt={item.description}
35 width={400}
36 height={300}
37 />
38 {/each}
39{/if}

Let's go through what's happening here. The first thing to note is that we're writing all of the TypeScript code in the <script> tag. You see that we do our package imports at the top of the tag to get the methods we'll need for the functionality, similar to importing packages at the top of component files in other frameworks.

Next, we create a state variable called images. This is what will hold all of the images we fetch from Cloudinary and we'll get to that part a bit later. Then we set another state variable called currentSlideItem. This updates the index for the current image every time a user clicks one of the buttons available.

After that, we write a couple of functions to handle users going back and forth between images in the carousel. They update the current image based on the length of the images array. Once we're outside of the <script> tag, then you can see some conditional logic.

This is the way you write conditions in Svelte. There is if-else-if notation and the other basic conditional statements. The big thing to pay attention to is that there are open and closing tags for the {#if} statement. Make sure you don't miss that closing tag!

Inside the {:else} statement, you'll see more conditional rendering. In this case, we're looping through the images array to display the images as users click through them. Only the current image is shown, but the rest are there, waiting for their turn.

With the main parts of this component in place, we need to add the call to our Cloudinary account to display the images.

Make the fetch request

We'll need to make an async call to the Cloudinary API when the component is loaded so that we have the expected images. This will tap into the Svelte component lifecycle because we'll need to use the onMount method so this call doesn't get stuck in a bad state. So inside the <script> tag, just below the images state variable, add the following code.

1<!-- Carousel.svelte -->
2...
3 let images = [];
4
5 onMount(async () => {
6 const results = await fetch(
7 `https://api.cloudinary.com/v1_1/${process.env.CLOUDINARY_CLOUD_NAME}/resources/image`,
8 {
9 headers: {
10 Authorization: `Basic ${Buffer.from(
11 process.env.CLOUDINARY_API_KEY +
12 ":" +
13 process.env.CLOUDINARY_API_SECRET
14 ).toString("base64")}`,
15 },
16 }
17 ).then((r) => r.json());
18
19 const { resources } = results;
20
21 images = resources.map((resource) => ({
22 url: resource.secure_url,
23 description: resource.public_id,
24 }));
25 });
26...

The request inside the onMount function is going to need your Cloudinary API credentials and you can get those from your Cloudinary settings. The request we're making will use your credentials to retrieve all of the images on your Cloudinary account. If you want to limit what gets returned, check out the API docs.

Now we're getting images directly from the API when the component loads for the first time. All we need to do now is display this new component to users!

Display the carousel

To do this, we need to make some changes to App.svelte. This has quite a bit of boilerplate code, so you can delete everything out of this file. Then replace it with the following code.

1<!-- App.svelte -->
2<script lang="ts">
3 import Carousel from "./lib/Carousel.svelte";
4</script>
5
6<main>
7 <h1>Hello People!</h1>
8
9 <Carousel />
10</main>
11
12<style>
13 :root {
14 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
15 Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
16 }
17
18 main {
19 text-align: center;
20 padding: 1em;
21 margin: 0 auto;
22 }
23 h1 {
24 color: #ff3e00;
25 text-transform: uppercase;
26 font-size: 4rem;
27 font-weight: 100;
28 line-height: 1.1;
29 margin: 2rem auto;
30 max-width: 14rem;
31 }
32
33 @media (min-width: 480px) {
34 h1 {
35 max-width: none;
36 }
37 }
38</style>

This imports our Carousel component and renders it on the page with a little message. A big difference between App.svelte and Carousel.svelte is that we have some styles defined in the <style> tag. You can almost think of these style tags like styled components if you've ever worked with that package.

Now if you run the app with npm run dev, you should see something like this.

That's it! Now you've made a commonly used component in a completely new framework. Take a minute and think about the differences and similarities between this and the framework you normally work with just to see how well you understand them both.

Finished code

You can check out all of the code in the image-carousel folder of this repo. You can also check out the app in this Code Sandbox.

Conclusion

It's nice to try out different frameworks to see what they do differently and see where you can take some pieces from one and apply them to others. Svelte is doing great as a tool developers like to use, so maybe one day we'll see more companies using it to build their apps. Plus it's good to know there's a framework out there that doesn't use the virtual DOM.

Milecia

Software Team Lead

Milecia is a senior software engineer, international tech speaker, and mad scientist that works with hardware and software. She will try to make anything with JavaScript first.