Building an Image Slider with Vue.js

An image slider is the quintessential use case for vue.js. It's a single, distinct component that requires data management, DOM updates, and user interaction. I recently needed a very simple image slider on a marketing page I was developing for a project built with vue.js. I rarely use jQuery any more, and since I was already using Vue for the rest of the project it seemed like a perfect opportunity to build one.

If you have never used Vue before, the official guide is quite good. I recommend understanding the basics before reading this tutorial. At the very least you should follow the installation instructions.

First we create our Vue object. We know we're going to need some images so we'll create an array of them in our object. We will also need a way to keep track of the active image, so we're going to create that variable too.

new Vue({
    el: 'image-slider',
    data: {
        images: ['http://i.imgur.com/vYdoAKu.jpg', 'http://i.imgur.com/PUD9HQL.jpg', 'http://i.imgur.com/Lfv18Sb.jpg', 'http://i.imgur.com/tmVJtna.jpg', 'http://i.imgur.com/ZfFAkWZ.jpg'],
        currentNumber: 0
    }
});

The element is named image-slider, which means in our HTML we can use the tag <image-slider></image-slider> to insert the slider anywhere we want to show it. Let's go ahead and do that, and also show the current image. Notice also the colon before the image's src attribute. That is Vue shorthand notation. It tells Vue that it should not literally read the attribute value, but should evaluate its contents. We are passing both the images array and the currentNumber variable to our component via its data object, making them available here.

<body>

<image-slider>
    <img :src="images[currentNumber]" />
</image-slider>

</body>

That's all that is needed to show the first image! Of course it doesn't do anything yet. We could allow the user to rotate through the images, but first we want to put them on a timer. Eventually we will want to stop the timer also, so we're going to add that now.

You expose methods to your component through the methods object. These are available in the object by using this.methodName(), and in the template by simply calling methodName in a bound attribute. Since we want the slider to start when the component is loaded, we hook into the Vue lifecycle's ready method to call trigger the rotation.

new Vue({
    el: 'image-slider',
    data: {
        images: ['http://i.imgur.com/vYdoAKu.jpg', 'http://i.imgur.com/PUD9HQL.jpg', 'http://i.imgur.com/Lfv18Sb.jpg', 'http://i.imgur.com/tmVJtna.jpg', 'http://i.imgur.com/ZfFAkWZ.jpg'],
        currentNumber: 0,
        timer: null
    },

    ready: function () {
        this.startRotation();
    },

    methods: {
        startRotation: function() {
            this.timer = setInterval(this.next, 3000);
        },

        stopRotation: function() {
            clearTimeout(this.timer);
            this.timer = null;
        },

        next: function() {
            this.currentNumber += 1
        }
    }
});

We used setInterval to run a 3 second timer. Every interval the next() method is called, which simply increments the currentNumber. Without changing the HTML, you will now see the page cycling through the images.

When the slider hits the end of the images it breaks. We need a way to cycle back to the start. The most direct way to do this is to check in the next method if the current number is larger than the length of the images array and set it back to zero if it is. If you've programmed for awhile you have probably discovered that the modulo operator is great for cycling. We're going to use it here, and also show an important Vue feature - that bound attributes are actually Javascript expressions.

<body>

<image-slider>
    <img :src="images[currentNumber % images.length]" />
</image-slider>

</body>

We now have a fully functioning image slider with just a few lines of JavaScript and some extremely simple HTML. And the vue.js library, of course. It's extremely simple, yes, but it is also a great foundation for building more features. Right away I'm thinking it could use some sliding effects; it should pause when you hover over it with your mouse; and some users might want to click through the images. Let's tackle the user interaction first, then we'll add an effect to it.

There are already methods to start and stop the rotation, and to progress to the next slide. All that is needed to rotate to the previous slide is to duplicate the next method, except decrease the current slide number.


    ...

        next: function() {
            this.currentNumber += 1
        },
        prev: function() {
            this.currentNumber -= 1
        }
    }
});

Do you see any problems with this? Since we are using the modulo operator for cycling our images, we're going to run into problems with negative numbers. We'll fix that by using the absolute value of the current number. We will also now add controls for clicking through the slides, and go ahead and add our pause on hover.

<body>

<image-slider>
    <p>
        <a @click="prev">Previous</a> || <a @click="next">Next</a>
    </p>
    <img
        :src="images[Math.abs(currentNumber) % images.length]"
        v-on:mouseover="stopRotation"
        v-on:mouseout="startRotation"
    />
</image-slider>

</body>

Notice that I use @click in the links, and v-on:mouseout for the image. These are the same thing! The @ notation is shorthand for v-on, similar to how the : notation is shorthand for v-bind.

Our image src is starting to get a little unwieldy. You could easily move that to a component method if you prefer to keep as much logic out of the template as possible.

There is also an issue with the slider timing when clicking next/prev. Since we don't reset the timer, sometimes it jumps immediately after clicking one of the controls. The simplest fix for this is to stop and start the rotation every time next/prev is called. At that point, we are not really using setInterval, and it would be simpler to use setTimeout since we then wouldn't have to stop the interval in the next and prev methods. I'll leave this decision to the reader.

Finally we want to add a small slide effect to the rotator. Vue already provides a transition system that does most of the complicated work for us. We just need to hook into it and add a little bit of CSS. Most of the details of how the transition system, and the CSS transitions work are already documented, so I won't go into a lot of detail here.

This transition does involve a little bit of trickery... I mean, clever coding. Other solutions may be more straight forward, but also less concise.

First we setup the CSS.

.fade-transition {
  transition: all 0.8s ease;
  overflow: hidden;
  visibility: visible;
  opacity: 1;
  position: absolute;
}
.fade-enter, .fade-leave {
  opacity: 0;
  visibility: hidden;
}

Within the component we will call our transition fade, so Vue has us declare three classes which it will apply/remove based on the status of the transition.

This is the new HTML required.

<body>

<image-slider>
    <p>
      <a @click="prev">Previous</a> || <a @click="next">Next</a>
    </p>
    <div
         v-for="number in [currentNumber]"
         transition="fade"
    >
      <img
          :src="images[Math.abs(currentNumber) % images.length]"
          v-on:mouseover="stopRotation"
          v-on:mouseout="startRotation"
      />
    </div>
</image-slider>

</body>

There are two big changes here. The important thing for the transition is the transition="fade". This is what tells Vue to use the CSS classes we defined.

Vue only runs transitions when an element is being inserted/removed, or shown/hidden. This includes cycling through each element when using v-for on a list. In our previous code we were not inserting a new picture to the DOM, but changing the src attribute for the same img element. Now we are telling Vue to cycle through an array - always containing a single value, set to the currentNumber value. When currentNumber changes Vue detects that change and redraws the v-for loop. We're basically tricking Vue into removing the old img element, and replacing it with the new. Since the fade transition is applied, and the divs are absolutely positioned, one image fades out while the next fades in.

Here is the final slider. Or you can play around with it on JSFiddle.


Originally published on

Published under development vuejs


Sign up to receive updates for new articles.
And spam. Definitely lots of spam.