DEV Community

Ben Winchester
Ben Winchester

Posted on

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 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;     // <---
...
Enter fullscreen mode Exit fullscreen mode
// src/App.svelte

<script>
...

<Carousel
    {images}
    imageWidth={250}
    imageSpacing={15}
  autoplay={true}              // <---
  autoplaySpeed={3000}         // <---
/>


<style>

</style>
...
Enter fullscreen mode Exit fullscreen mode

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. The onDestroy 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()})
...
Enter fullscreen mode Exit fullscreen mode

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}
  ...

Enter fullscreen mode Exit fullscreen mode
// 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}   // <---
/>
...

Enter fullscreen mode Exit fullscreen mode

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}
/>
...

Enter fullscreen mode Exit fullscreen mode
// 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}                                           // <---
...

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)