Building a Simple Image Slider with Svelte

Sat, Sep 18, 2021

Note: while this was a fun exercise, it’s a bit outdated. The core concept using CSS Custom Properties is still solid though. But if you need a good carousel component, checkout svelte carousel.

Why

There are plenty of image slider/carousel components and libraries around. So why would I want to build one myself?

  1. It’s fun! And it’s a great way to learn any framework you’re using.
  2. We’ll be taking advantage of CSS Custom Properties; I’ve been having so much fun exploring these and their uses.

How

This is nothing new or groundbreaking. But I think it’s a fun little project. So here we go…

We’ll use Svelte’s REPL to create it without the need to install anything locally.

We’ll need to create a new Svelte component. Slider.svelte and start with the html structure of the slider:

<div class="slider-container">
  <div class="slider">
      <slot></slot>
  </div>
</div>

Svelte offers slots to allow us to access a component’s child content similar to children props in React. Here we are using a slot to capture the Slider’s content.

Next, we’ll add some variables in our script tag to start capturing and managing our slider properties. For our slider, we’ll need to know

  1. how to get a reference to your slider element
  2. how many child elements are in the slider
  3. what element we are currently viewing

Above our html, we can add a script tag and initialize our variables:

<script>
	let slider; // a reference to our slider
	let index = 0; // the current child element's index, initialized to 0 as we will be storing them in an array
	let length = 2; // the amount of child elements, initialized to 2 as we will need at least 2 elements to slide
</script>

Now that we have a variable, slider, to reference our slider, we need to use Svelte’s bind directive, in particular bind:this:

<div class="slider-container">
  <div class="slider" bind:this={slider}>
      <slot></slot>
  </div>
</div>

Since we can access the slider element, we update our length property which is the length of child elements in our slider. However, we can’t access these until our component has moounted. To do this, we need to use Svelte’s onMount which “schedules a callback to run as soon as the component has been mounted to the DOM.”

At the top of our component, we can import onMount

import { onMount } from 'svelte';

Then we can update our length value:

	onMount(() => {
		length = slider.children.length;
	});

We will pause the Svelte stuff now and style our component so we can see only one slide at a time. In our main app, we need to import the slider component and add some children:

<script>
	import Slider from './Slider.svelte';
</script>

<Slider>
	<div>
		hello
	</div>

	<div>
		hola
	</div>

	<div>
		q'vo
	</div>
</Slider>

And back in our slider component, we can start styling. We want the .slider-container to have a full width and overflow hidden to hide slides outside of the view.

<style>
	.slider-container {
		width: 100%;
		overflow: hidden;
	}
</style>

The .slider container will contain most of the styles that will make the slider, well, slide. And here is where we will use CSS Custom Properties, or CSS variables. Just like we initialized our index and length variables in our component JS, we want to initialize those same properties in our CSS:

<style>
	.slider-container {
		width: 100%;
		overflow: hidden;
	}

	.slider {
		height: auto;
		display: flex;
		align-items: center;
		justify-content: space-around;
		position: relative;
		transition: all .5s ease-in-out;

		--index: 0;
		--length: 2;
		left: calc(-100% * var(--index));
		width: calc(100% * var(--length));
	}
</style>

CSS custom properties are written as --my-custom-prop and can take any valid CSS value. In our case, we set up an --index and a --length with the same inital values as our JS variables. We are using CSS’ calc() function to determine the width and left position of the .slider div. The logic is fairly simple. When our index is 0 we multiply -100 * 0 putting our div at the left: 0 position. So if we have 4 slides, the index increases by 1 and our left property will be at that index value times -100. If we are at slide #3, it will have index of 2 and left: -200%.

But how will our custom properties update? A bit of JS and Svelte is all it takes. We’ll use Svelte’s reactive statement to keep track of our index and length values. If you’re not familiar with Svelte’s reactive statement, the Svelte docs explain it thusly:

Any top-level statement (i.e. not inside a block or a function) can be made reactive by prefixing it with the $: JS label syntax. Reactive statements run immediately before the component updates, whenever the values that they depend on have changed.

It basically acts as a computed property of our component, updating whenever its dependent values change. So we can create a styles variable as a reactive statement to store index and length values:

$: styles = `--length: ${length}; --index: ${index}`;

This esures that whenever either index or length values change, so does the value of styles. Notice we are storing these values in a string as values of our CSS custom properties. So whenever our length and index change, our styles variable updates thereby updating our --length and --index custom properties. We can now pass those as inline styles to our slider component like so:

<div class="slider-container">
  <div class="slider" bind:this={slider} styles={styles}>
      <slot></slot>
  </div>
</div>

We have no way of updating the JS variables, though, so we’ll add some buttons to update them. We need to increase the index value with a “next” button and decrease it with a “previous” button.

<button on:click={prev}>
	&larr;
</button>
<button on:click={next}>
	&rarr;
</button>

Next, we need to define our prev and next functions. Again, all they are doing is updating our index value:

	const next = () => index++;
	const prev = () => index--;

Our slider should be working now but there is one small problem. Our slider keeps sliding even if we’re out of slides. An easy fix is to disable our buttons when there are no more slides. So if our index is 0 our “previous” button should be disabled. And when index hits length - 1 our “next” button should be disabled.

<button on:click={prev} disabled={index === 0}>
	&larr;
</button>
<button on:click={next} disabled={index === length - 1}>
	&rarr;
</button>

And we are done with a simple slider. You can check out the final code here: https://svelte.dev/repl/0f848c5af8f54628bee58a7a4472af94?version=3.43.0

Again, this is very simple but could easily be extended. Some ideas are

  1. add indicators for each slide
  2. make it circulate infinitely
  3. add a prop to the slider to auto scroll

The point is to have fun and explore.


Back to blog