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?
- It’s fun! And it’s a great way to learn any framework you’re using.
- 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 slot
s 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
- how to get a reference to your slider element
- how many child elements are in the slider
- 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}>
←
</button>
<button on:click={next}>
→
</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}>
←
</button>
<button on:click={next} disabled={index === length - 1}>
→
</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
- add indicators for each slide
- make it circulate infinitely
- add a prop to the slider to auto scroll
The point is to have fun and explore.