Creating a multi effect slider from scratch

Have you ever seen those multi effect sliders that magically switch effects on every slide? One minute it’s a simple fade, the next a bunch of 3D slices rotate in and reveal the next slide. Have you ever wondered how the slider was pulling this off? Well, wonder no more! Today I will show you how to build such a slider.

[button color=”green” size=”medium” link=”http://codepen.io/Arnique/pen/aOxWxW” ]View Demo + Code[/button]

First the setup

For starters we will need to add a number files to our page. Our slider will be powered by jquery, so we add the latest version first. Other required files will be Daniel Eden’s animate.css to power our animations and fontawesome fo our control icons. We will also be creating our own CSS and Javascript files to support our slider. The full list is shown below:


<!-- Required CSS files -->
<link href="path/to/animate.min.css" rel="stylesheet">
<link href="path/to/font-awesome.min.css" rel="stylesheet">

<!-- CSS Files to be created -->
<link href="path/to/slider.css" rel="stylesheet">

<!-- Required JS files
<script src="path/to/jquery.min.js"></script>

<!-- JS Files to be created -->
<script src="path/to/slider.js"></script>
<script src="path/to/effects.js"></script>
<script src="path/to/sort.js"></script>

The markup

Before we start coding away, we will need to add some markup for our slider. We also take this opportunity to name our slider. We give it the name “Poly Slider”, and use “poly-” as our class prefix. The markup structure we will be using is shown below.


<div id="demo" class="poly-slider">
<!-- Slides -->
<ul class="poly-viewport">
  <li class="poly-slide">
    <img class="poly-bg" src="images/pic1.jpg" alt="">
  </li>
  <li class="poly-slide">
    <img class="poly-bg" src="images/pic2.jpg" alt="" />
  </li>
  <li class="poly-slide">
    <img class="poly-bg" src="images/pic3.jpg" alt="" />
  </li>
</ul>
<!-- Navigation controls -->
<a class="poly-nav back"><i class="fa fa-angle-left"></i></a>
<a class="poly-nav forward"><i class="fa fa-angle-right"></i></a>
</div>

As shown, our slides will be contained inside an un-ordered list, with the parent ul tag acting as the viewport. You can swap the ul > li structure with a div > div structure if you want.

The CSS

The slider styles will be stored in our slider.css file. The most important bits are shown below. The rest can be viewed in the code demo.


/* SLIDER SETUP */
.poly-slider {
  position: relative;
  width: 100%;
  display:block;
}
.poly-viewport {
  display:block;
  list-style:none;
  margin:0;
  padding:0;
  overflow: hidden;
  width: 100%;
  height:100%;
  position: relative;
  perspective: 1000px;
}
.poly-slide {
  display:block;
  position: absolute;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  z-index:1;
  visibility: hidden;
}

/* SLIDE STATES */
.poly-slide.active, .poly-slide.incoming {
  visibility: visible;
}
.poly-slide.incoming {
  z-index: 2;
}
.poly-slide.z-top {
  z-index:3;
}

There is not much going on here. We de-list our unordered list and give it a perspective, then let our slides lose by setting their positions to absolute. This way each slide will be able to move around freely without disturbing adjacent slides. We also create a few helper classes that change the visibility and z-indexes of our slides and slices.

The code

Instead of packing everything in one file, We will be breaking up the main functions and storing them in separate files. Our slider.js file will only contain the slider logic while our effects will be stored inside effects.js. Our other file sort.js, will contain our slice sorting methods. We then declare a single namespace “ps”, to “glue” all these files together. Using this pattern ensures that we can easily add new effects and sorting methods without breaking our slider.

The plugin skeleton

Our top level file will be slider.js, so we declare our namespace here then “glue” our future effects and sorting methods to it. This is also where we will write our plugin code. But before we get carried away, it would be wise to create a “skeleton” first, then add on more functionality as we go along. The initial state of slider.js is shwon below.


var ps = {};
ps.effect = {};
ps.sort = {};

(function ($) {
$.fn.polySlider = function(userOptions) {

  // Defaults
  var defaults = {};

  // Public and private members
  var pub = {};
  var pvt = {};

  // Cache main selector
  pub.el = $(this);

  // Init
  pvt.init = function() {
  // Extend options
  pub.options = $.extend({}, defaults, userOptions);

  // Cache selectors
  pub.viewport = pub.el.find(".poly-viewport");
  pub.slides = pub.viewport.children();
  pub.navForward = pub.el.find(".poly-nav.forward");
  pub.navBack = pub.el.find(".poly-nav.forward");

  // Count slides
  pub.total = pub.slides.length;

  // Bind Forward ctrl
  pub.navForward.on("click", function(e) {
    e.preventDefault();
  pub.goForward();
  });

  // Bind Back ctrl
  pub.navBack.on("click", function(e) {
    e.preventDefault();
  pub.goBack();
  });
  }

  // Go forward
  pub.goToSlide = function(index) {
    alert("going to slide #" + index);
  };

  // Go forward
  pub.goForward = function() {
    alert("going forward");
  };

  // Go Back
  pub.goBack = function() {
    alert("going back");
  };

  // Execute
  pvt.init();

  // Expose public members to the user
  return pub;
  };
})(jQuery);

As shown, we are applying a simple technique to create public and private “areas”. Anything we want to keep private is saved inside the pvt variable, while anything else we want to expose to the public is saved in the pub variable. To “expose” our public members, we simply return the pub variable after executing our code.

Our initial code gives life to our plugin. You can even do a quick test it out by calling polySlider().


var demo = $("#demo").polySlider({test:"hello world!"});

// try checking for some options
console.log(demo.options.test)

// try calling some public methods
demo.goToSlide(6);

// try calling some forbidden private methods.
demo.init(); // should fail!

Now that we have a functioning plugin, we can go ahead and do the following:

  • Add some default options
  • Provide a method to read and set the dimensions
  • Extract the background images for each slide
  • Slice these images and arrange them on top of each slide
  • Select the first slide so that it’s visible.

To keep things short, I will not be showing you every piece of code, especially the volumenous bits like the ones above. Instead, I will just be showing you the relevant ones, then provide the complete code at the end.

Performing our very first slide

At this point, we have all the ingredients to perform a slide. We will enable a basic slide first, then later on add support for CSS based transitions. Let’s go back to our skeletal goToSlide() method and add some meat.


// Sliding function
pub.goToSlide = function(index, effectName, dir) {
  // Police all requests
  if(pub.isWorking || index == pub.activeIndex) return;

  // Declare status
  pub.isWorking = true;
  pub.el.addClass("is-working");

  // Set Direction
  if(dir) {
    pub.direction = dir;

  } else {
    pub.direction = (index > pub.activeIndex)? "forward" : "back";
  }

  // Select slides
  pub.nextIndex = index;
  pub.activeSlide = pub.slides.eq(pub.activeIndex);
  pub.nextSlide = pub.slides.eq(pub.nextIndex);

  // Switch slides
  pub.nextSlide.addClass("active");
  pub.activeSlide.removeClass("active");

  // Set new index
  pub.activeIndex = pub.nextIndex;

  // Reset status
  pub.isWorking = false;
  pub.el.removeClass("is-working");
}

// Go forward
pub.goForward = function(effectName) {
  var i = (pub.activeIndex + 1) % pub.total;
  pub.goToSlide(i, effectName, "forward");
};

// Go Back
pub.goBack = function(effectName) {
  var i = (pub.activeIndex - 1) % pub.total;
  if(i < 0) i += pub.total;
  pub.goToSlide(i, effectName, "back");
};

The above code is fully functional and will switch slides when goToSlide() is called. Both goForward() and goBack() point to goToSlide() so as to avoid repetitive code. The % character in the code is the modulo operator, and we use it to police the slide index using modular arithmetic.Think about it:


// A total of 4 slides should cycle between indexes 0 to 3. i.e
0, 1, 2, 3, 0, 1, 2, 3...and so on
// The above cycle can be easily reproduced using modula arithmetic
0 % 4 = 0
1 % 4 = 1
2 % 4 = 2
3 % 4 = 3
4 % 4 = 0
5 % 4 = 1
6 % 4 = 2
7 % 4 = 3
8 % 4 = 0
// Even if we moved 787 steps forward, our formula still works!
787 % 4 = 3

Effects setup

Our effects will be stored inside the effects.js file. A single effect will be stored as JS object as shown below:


ps.effect.fader = {
  forwardIn: "fadeIn animated",
  forwardOut: "fadeOut animated",
  backIn: "fadeIn animated",
  backOut: "fadeOut animated",
  dur: 600,
};

The dur value sets the effect duration in milliseconds, while the rest are CSS classes that are applied to the slides based on the following rules

  • forwardIn is applied to the incoming slide when sliding forward.
  • forwardOut is applied to the exiting slide when sliding forward.
  • backIn is applied to the incoming slide when sliding backwards.
  • backOut is applied to the exiting slide when sliding backwards.

Since we are using animate.css as our library, we have to add the “animated” class to each animation. If you are using a different CSS animation library, then the same rules apply. Just plug in the relevant class names and you are good to go!

But what about sliced effects?

Sliced effects work just like regular effects. The only difference is that the effect classes are applied to incoming and exiting slices, not the slides. Let us look at the markup of an individual slice to better understand this.


<div class="poly-slice">
  <div class="exit"></div>
  <div class="entry"></div>
</div>

As you can see, each slice acts like a mini slider, and holds a pair of identical slices that can be individually transitioned. The “entry” slice plays the part of the incoming slide, while the “exit”
slice plays the active slide. So in essence, any defined slide effect can be equally applied to individual slices. This is the secret behind those little 3D cubes!

Because slices can use slide effects, a slice effect only has to refer to a predefined effect rather than individual classes. So if we wanted to create a sliced effect based on our “fader” effect, then we would do it as follows:


ps.effect.slicedFader = {
  sliced: true,
  dur: 600,
  delay: 50,
  effect: "fader",
};

You may have noticed some new properties. Let me explain:

  • dur is the animation duration in milliseconds that each slice should take.
  • delay is the time each slice will be incrementally delayed before it animates.
  • effect is the pre defined slide effect to refer to
  • sliced just tells our slider that it’s handling a sliced effect

Sorting slices

By default, the slices are arranged from top to bottom, with their indexes increasing from right to left. When applying effects, our slider will select the slices in this same order. However, we can write our slice animating function to instead use a “map” of indexes to select the slices. This way, we can create a variety of “maps” with different arrangements.

slice-sort.gif

As shown above, to make the slice effects flow in a particular direction, we have to arrange our indexes in a very specific order. We delegate the job of arranging these indexes to array sorting functions we define inside our sort.js file. Each sort function is saved under our “ps” namespace to make it accessible to our slider. Here are 2 examples of some sorting functions:


// Flow down to the right
ps.sort.flowDownRight = function(rows,cols) {
  var sorted = [];
  for(var y=0; y < cols ; y++) {
    for(var x=0; x < rows ; x++) {
      var yVal = y;
      var xVal = x;
      var v = (xVal * cols) + yVal;
      sorted.push(v);
    }
  }
  return sorted;
}

// Flow down to the left
ps.sort.flowDownLeft = function(rows,cols) {
  var sorted = [];
  for(var y=0; y < cols ; y++) {
    for(var x=0; x < rows ; x++) {
      var yVal = cols - y -1;
      var xVal = x;
      var v = (xVal * cols) + yVal;
      sorted.push(v);
    }
  }
  return sorted;
}

Now, it must be said that there is no correct way of writing a sorting method. What’s important, is that you are be able to produce a predictable order of valid indexes, regardless of the current slice formation.I personally find that the above “loop” method allows me to “visualize” what I’m doing.

With our functions defined, we can simply tell our slider what method to use when sorting our slices.


// Pass the option
$("#demo").polySlider({sliceSort:"flowDownRight"});

// Then somewhere in our code
var map = ps.sortpub.options.sliceSort;

// Animate the mapped slices
for(var i=0; i < pub.totalSlices; i++) {
  var $slice = pub.nextSlide.data("slices").eq(map[i]);
  // < slice animating code here >
}

That’s it!

If you’re itching to test drive the complete slider then I’ve setup a codepen demo in the link below.

 

Demo + Code

While you are examining the code, you may want to try the following.

  • Create new effects using a different animation library.
  • Improve the default sort methods.
  • Write or new sort methods. Perhaps even a circular one?
  • Add more options.
  • Allow the use of solid backgrounds if images are not present.

Obviously, there is still a lot of missing functionality in our slider. There is also need for some code refactoring to improve efficiency. However, as it stands we have functional proof of concept that we can build upon.

Cheers.