Build an Image Carousel with Svelte - Part 2 (Adding Features)
Some notes from Part 1 This article picks up from a previous article, if you haven't read...
Some notes from Part 1
This article picks up from a previous article, if you haven't read it already please go back and check it out. The repo is also available here
I posted the article and youtube video in a few places online and received some feedback on the usefulness of Carousels. This series is not intended to advocate the use of Carousels or comment on their impact on user experience. This is more of a way to introduce some of the functionality of Svelte. In part 1 we:
- Downloaded the svelte template
- Created a component
- Pass props from parent to child component
- Explored conditional rendering with the
{#for <Iterable> as <Reference>}{/for}
loop - Implemented event handling and Svelte's directive syntax
- Implemented slots to place child components
The goal was to get introduced to Svelte as tool with something other than the standard "Hello World" or "Todo" application.
In this continuation we will add a features to enable autoplay, allow the user to disable the control buttons, in the process we will:
- Set and Clear intervals
- Further explore conditional rendering logic with
{#if}{/if}
syntax - Introduce
onDestroy
lifecyle method - Demonstrate custom events and dispatching data from child to parent components
As will the last article, this one coincides with a the following Twitch stream now available on my YouTube channel:
Lets add some features
Adding autoplay capability
First we need to expose a prop variable by exporting it in the Carousel.svelte
file. We will set it to false
as a default, and then update our Component declaration to set it as true
so we can see it while we build. We also want to pass in a speed at which the autoplay will occur, so we expose autoplaySpeed
as prop with a default value of 5 seconds, though we will override it by passing 3000 milliseconds to the prop for demonstration purposes.
// src/components/Carousel.sveltejs
<script>
import { flip } from 'svelte/animate';
export let images;
export let imageWidth = 300;
export let imageSpacing = 20;
export let speed = 500;
export let controlColor= '#444';
export let controlScale = '0.5';
export let autoplay = false; // <---
export let autoplaySpeed = 5000; // <---
...
// src/App.svelte
<script>
...
<Carousel
{images}
imageWidth={250}
imageSpacing={15}
autoplay={true} // <---
autoplaySpeed={3000} // <---
/>
<style>
</style>
...
Back in our component file we will create the startAutoPlay
and stopAutoPlay
functions which will will... you know, control our autoplay. Then we will run a check to see if autoplay
is set to true, and if so, call the startAutoPlay
.
The startAutoPlay
will set an interval to call the rotateLeft
function we wrote in part 1. The stopAutoPlay
will clear the intervals. Be sure to also check if autoplay
is enabled in the startAutoPlay
function to ensure it isn't mistakenly started after a mouseover.
IMPORTANT In the video stream I forgot to discuss the
onDestroy
lifecyle method. TheonDestroy
lifecyle method is called when the component is unmounted. Remember to add this to insure the interval is cleared.
// src/components/Carousel.sveltejs
...
import { onDestroy } from 'svelte';
...
const startAutoPlay = () => {
if(autoplay){
interval = setInterval(rotateLeft, autoplaySpeed)
}
}
const stopAutoPlay = () => {
clearInterval(interval)
}
if(autoplay){
startAutoPlay()
}
onDestroy(() => {stopAutoPlay()})
...
We now have a functioning autoplay feature!
Custom Events and Dispatching from Child to Parent component
Because this is a demonstration, it is only visible in the demonstration/eventDispathChildToParent branch of the repo.
Just like with a normal on:<EventName>
event listener directive, we can create our own events. So we will add the on:imageClicked
to the Carousel declaration in the App.svelte
file. Now the Carousel will listen for the imageClicked
event that is emitted from within. For now we will handle the event by calling a handleImageClicked
function which will log the event to the console.
Inside the Carousel.svelte
file we will have to create an event dispatcher. First we must import createEventDispatcher
from the svelte package. Now we will store it in a variable called dispatch
, and add it to each <img>
tag that is rendered in the {#each}
loop.
In Svelte, an event dispatcher takes two parameters, the first is the signal you emit, and the second is called the events detail
and will be accessible as and attribute of the event. Here we will call dispatch('imageClicked', image.path)
to each <img>
tag. This will emit the images path as the events detail.
Lastly we will update our App.svelte
so that it doesn't log "image clicked", but instead will log the clicked image's path.
// src/components/Carousel.svelte
<script>
import { flip } from 'svelte/animate';
import { createEventDispatcher, onDestroy } from 'svelte'; // <---
export let images;
export let imageWidth = 300;
export let imageSpacing = 20;
export let speed = 500;
export let controlColor= '#444';
export let controlScale = '0.5';
export let autoplay = false;
export let autoplaySpeed = 5000;
export let displayControls = true;
let interval;
const dispatch = createEventDispatcher() // <---
...
{#each images as image (image.id)}
<img
src={image.path}
alt={image.id}
id={image.id}
style={`width:${imageWidth}px; margin: 0 ${imageSpacing}px;`}
on:mouseover={stopAutoPlay}
on:mouseout={startAutoPlay}
on:click={() => dispatch('imageClicked',image.path)} // <---
animate:flip={{duration: speed}}/>
{/each}
...
// src.App.svelte
const handleImageClicked = e => { // <---
console.log(e.detail) // <---
}
</script>
<Carousel
{images}
imageWidth={250}
imageSpacing={15}
controlColor={'white'}
controlScale={0.8}
autoplay={true}
autoplaySpeed={3000}
on:imageClicked={handleImageClicked} // <---
/>
...
Now when you click on an image, you will see its path logging to console with the source being App.svelte
Make the controls disappear
For the rest of the article I will be working off the master branch, so dispatch examples will not be visible
Lastly, we will expose another boolean variable in the Carousel.svelte
file called displayControls
and give it a default value of true
. However, we will pass false to the prop from the component declaration so we can see it disappear.
Now we can wrap both button elements in the Carousel.svelte
with the {#if <Expression>}{/if}
conditional logic, and watch the controls disappear.
// src.App.svelte
...
<Carousel
{images}
imageWidth={250}
imageSpacing={15}
controlColor={'white'}
controlScale={0.8}
displayControls={false} // <---
autoplay={true}
autoplaySpeed={3000}
/>
...
// src/components/Carousel.svelte
...
{#if displayControls} // <---
<button id="left" on:click={rotateLeft}>
<slot name="left-control">
...
</slot>
</button>
<button id="right" on:click={rotateRight}>
<slot name="right-control">
...
</slot>
</button>
{/if} // <---
...
Conclusion
Thanks for following the series. In future articles I will continue with stores
, Sveltes context API for sharing data with adjacent components.
If you are interested in other topics surrounding Svelte leave a comment.