Blog

Building a Responsive jQuery Carousel Plugin from Scratch

Tags: development, front-end, jquery, responsive.

Barrel_Blog_Developing_jQuery_Plugin_Cover

Developing a jQuery plugin shouldn’t be seen as something reserved only for highly-experienced developers. If you know how to write a little JavaScript and understand how a JavaScript object works, you’re half-way there. This article aims to guide you through the last half.

Throughout this article we’ll be building a responsive image carousel. We’ll be doing this as we explore my plugin development process, demonstrating how to progress from an idea to a fully developed lightweight plugin ready for use in professional projects. We’ll use CSS3 animations for performance benefits with JavaScript animation fallbacks to provide browser compatibility for IE7, IE8 and IE9.

The finished plugin: http://codepen.io/barrel/pres/oBefw

Let’s start the process.

Research

There are many jQuery carousel plugins available today, so my research process involved sifting through the badly written and outdated to gather a few great examples of effective plugins. Pairing these with my own experience, I formed a list of requirements which–after hashing out the logic behind the plugin–will inform the plugin skeleton or functional breakdown of the plugin.

Responsive requirements:

  • No inline styles. The carousel needs to scale as the browser window resizes. If we hard-code inline dimensions to any DOM element we may run into overflow problems. Really, we should be restricting the carousel container to a percentage-based dimension with all children sized proportionately to this container.
  • The aspect ratio must be maintained.We need to ensure that the height resizes proportionately to the width as the width changes size.

Flexibility requirements:

  • The carousel must be able to present anything from images to HTML slides.
  • The plugin must be usable by more than one element on the same page.

Configuration requirements:

  • The developer must be able to change transition timings easily.

Usability requirements:

  • The user must be able to move to previous and next slides (via arrows or pagination indicators).
  • The user must be able to move to any slide via pagination indicators.
Sketch

Working with graphite on paper is a great way to provide a visual representation for complex problems. I do this a lot when building out custom UI elements, elements that dependent on multiple forms of user input or have two or more DOM elements animating at the same time. I have grown to value the sketching stage as a time to step away from the desk and recharge while entertaining whatever abstract idea I have in my head.

For me, the sketching stage sits comfortably beneath the research stage, since we already have a list of requirements ascertained and can base our visualisation of the problem on actual constraints. After all, we sketch to provide answers, so the problem must be understood first.

Annotation:

  • The active slide always sits above all other slides.
  • The next slide is placed to the right (or left) of the current slide, outside of the viewport, to slide into view.
  • Both slides are animated in unison to produce the sliding effect.
  • At the end of the slide-in animation, the previously active slide moves to join the stack of slides behind the active slide.
Barrel_Blog_Developing_jQuery_Plugin_1
Create The Project Scaffold

Here, we outline the plugin by mapping out empty functions and essential variables. It’s very similar to writing an Object Interface in PHP. I depend on this step for a wide range of projects, regardless if I’m developing the directory and file scaffold for a new WordPress theme or, in this case, a set of JavaScript functions to tackle a specific task. The last thing we want is an incoherent set of nested functions filled with redundant code and unnecessary dependencies. What we do want is a logical structure mapped out so other developers can easily understand the action flow.

By keeping our focus high level for the time being, we can pay attention to the type of information our functions will accept as arguments, and the specific tasks our methods will execute. We should be considering what we need the value of ‘this’ to be for each function. I’d recommend composing descriptive comments for each method, both for others and yourself.

Throughout this step, you should be asking yourself:

  • How easy will it be to add more functionality to the task?
  • How easy is it going to be for another developer to understand what is happening?

There are many ways to structure a jQuery function and the architecture detailed below is just one example. This way, we leverage JavaScript’s object-orientated nature. We use jQuery for efficiency; for communicating with the DOM, setting up the the plugin’s settings and animating via Javascript. With a little more work, we could remove jQuery as a dependency.

;(function(factory){
  
  factory(jQuery);

})(function($){ 
   
  /*
   * We define Zippy as a variable of type ‘function’. 
   * Here, we use an anonymous function to ensure 
   * that the logic inside the function is executed immediately. 
   *
   */
  var Zippy = (function(element, settings){
    
    var instanceUid = 0;
    
    /*
     * The constructor function for Zippy
     *
     */
    function _Zippy(element, settings){
      this.defaults = {};
      
      // We create a new property to hold our default settings after they
      // have been merged with user supplied settings
      this.settings = $.extend({},this,this.defaults,settings);
      
      // This object holds values that will change as the plugin operates
      this.initials = {};  
      
      // Attaches the properties of this.initials as direct properties of Zippy
      $.extend(this,this.initials);
      
      // Here we'll hold a reference to the DOM element passed in
      // by the $.each function when this plugin was instantiated
      this.$el = $(element);
      
      // Ensure that the the value of 'this' always references Zippy
      this.changeSlide = $.proxy(this.changeSlide,this);
      
      // We'll call our initiator function to get things rolling!
      this.init();
      
      // A little bit of metadata about the instantiated object
      // This property will be incremented everytime a new Zippy carousel is created
      // It provides each carousel with a unique ID
      this.instanceUid = instanceUid++;
    }
    
    return _Zippy;
  
  })();
  
  /**
   * Called once per instance
   * Calls starter methods and associates classes
   */
   Zippy.prototype.init = function(){};
	
  /**
   * Test to see if CSSTransitions are available
   *
   */
   Zippy.prototype.csstransitionsTest = function(){};
	
  /**
   * Build out any necessary DOM elements like slide indicators
   *
   */ 
   Zippy.prototype.build = function(){};
	
   /**
    * Activate the first slide
    */ 
    Zippy.prototype.activate = function(){};
	
   /**
    * Associate event handlers to events
    *
    */
    Zippy.prototype.events = function(){};
	
   /**
    * Clear timer
    *
    */
    Zippy.prototype.clearTimer = function(){};
	
   /**
    * Initialize the timer
    *
    */
    Zippy.prototype.initTimer = function(){};
	
   /**
    * Start the timer
    *
    */
    Zippy.prototype.startTimer = function(){};
	
   /**
    * Control the logic behind transitioning to the next slide
    * - Determine in what direction we need to animate
    * - Determine which slide will be active next
    *
    */
    Zippy.prototype.changeSlide = function(event){

  /**
    * Control the CSS animations
    *
    */
    Zippy.prototype._cssAnimation = function(nextSlide,direction){};
	
   /**
    * Control the JS animations
    *
    */
    Zippy.prototype._jsAnimation = function(nextSlide,direction){};
	
   /**
    * Update the slide indicators once each slide animation has ended
    *
    */
    Zippy.prototype._updateIndicators = function(){};
	
   /**
    * Initialize the plugin once for each DOM object passed to jQuery
    * @params	object	options object
    * @returns void
    *
    */
    $.fn.Zippy = function(options){
    
       return this.each(function(index,el){
      
      el.Zippy = new Zippy(el,options);
      
    });
    
  };
  
});

// Custom options for the carousel
var args = {};

$('.carousel').Zippy(args);

You can see how much time goes into this section. We attempt to map out all the individual snippets of functionality that we will need in order for this plugin to run successfully. We may add new methods down the track (I added a few new methods to dynamically add and remove CSS animation durations and I abstracted the changeSlide method out into more specific functions) but we try to get as close as we can to the finished project scaffold.

Let’s run through the important architectural considerations, starting from the top of the file and working our way down. Here, I am going the cherry pick certain sections of the architecture out from the main code to better explain them. I’d recommend playing around with the codepen example to help get a better understanding of how everything works.

1. We pass in a function as an argument to an anonymous function. Inside the  anonymous function we call the function and pass it the jQuery object.

;(function(factory){
  
  factory(jQuery);

})(function($){ /*Function Logic Here*/ });

2. We define Zippy as a variable of type function. Here, we use an anonymous function to ensure that the logic inside the function is executed immediately and the scope of nested variables are localized to the function only.

var Zippy = (function(element, settings){
    
  var instanceUid = 0;
  
})();

3. We return a variable _zippy() of type function. This is the constructor function. Here, we’ll set up the plugin settings, cache a reference to the DOM element that the plugin will manipulate and lastly call this.method() to execute plugin functionality. We also give every instance of the plugin a unique ID. Although we are not using this property in this article, we would use it as a reference point if we had a ‘destroy’ method to detach the plugin dynamically.

 var Zippy = (function(element, settings){
    
  var instanceUid = 0;
  
  function _Zippy(element, settings){
    this.defaults = {};
    
    this.settings = $.extend({},this,this.defaults,settings);
    
    this.$el = $(element);
    
    this.method();
    
    this.instanceUid = instanceUid++;
  }
  
  return _Zippy;

})();

4. After defining the variable Zippy, we use Zippy’s prototype to add new methods. We use the prototype method to attach new functionality to an object after it has been declared. Declaring new methods through this.method is essentially the same as this.prototype.method, except that when we define new methods with this.method, the method is completely re-created every time the object is instantiated, whereas this.prototype attaches the method to the class that the object is instantiated from, so it allows for more efficient code execution.

var Zippy = (function(element, settings){
    
  function _Zippy(element, settings){
    
    this.method();
  }
  
  return _Zippy;

})();

Zippy.prototype.method = function(){};

5. We attach a new method Zippy to the jQuery object and when called, instantiate a new instance of variable Zippy and attach it to every DOM element referenced by the jQuery selector, in this example every DOM element with class “carousel”.

$.fn.Zippy = function(options){
    
  return this.each(function(index,el){
    
    el.Zippy = new Zippy(el,options);
    
  });
  
};

You’ll notice I left out some points. How are we actually storing settings, and what is ‘this’ referencing? Bring in $.extend and $.proxy respectively.

$.extend is the simpler function to get your head around. It merges the contents of two or more objects together into a target object. Here, we use it to compare the properties of two objects, this.defaults and settings. We overwrite any corresponding default properties with user supplied settings. We pass the merged properties into an empty object (the first argument) before saving it to this.settings once the function is complete.

$.proxy is used to manipulate the value of ‘this’ within a function. ‘this’ is one of the most powerful keywords available in JavaScript, but it can be hard to fully comprehend. Simply put, ‘this’ references the owner of the function that is being executed. In the context of this plugin, I want the value of ‘this’ in all prototype methods to point to the plugin itself, so I use $.proxy to return a variation of a function (in this case, the changeSlide method) with the value of ‘this’ always pointing back to Zippy. It is relevant to the changeSlide method as changeSlide is going to be called through $.on() and setInterval() which change the value of ‘this’ to the window object.

Reference:

Method Writing

With the requirements nicely laid out, the logic sketched up, and the structure of the plugin mapped out, we’re in a great position to get specific. Developing succinct and readable code is important, so we’re going to focus on declaring as few variables as possible and providing clear names. We should also pay attention to the white space we insert between lines; the carriage return is a great tool to group and separate lines of code for legibility.

For this stage, I am going to rely on in-code commenting to explain the methods, so work your way through the code and feel free to play around with the Codepen example. As with any front-end project, refined CSS is essential. I have commented the plugin’s CSS with-in the Codepen example. I’d recommend taking the time to review and understand why each CSS property exists as each one is needed for the carousel to function correctly.

http://codepen.io/barrel/pres/oBefw

Wrapping Up

Finally, we’ll talk quickly about preparing a plugin for modular javascript definition. Using a plugin that can easily be adapted to a modular JavaScript architecture saves me a considerable amount of time with-in the development phase of projects. The more complex my JavaScript becomes, the more I want to break my JavaScript code into individual files that focus on individual areas of a website. I’ll have a module (a separate JS file) that controls any pre-loading animations, a module that handles form submissions, etc. As this becomes a more popular way of writing JavaScript, modern jQuery plugins should support the libraries that make modular development possible.

Let’s look at the code at the top of the JavaScript file.

if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
} else if (typeof exports !== 'undefined') {
    module.exports = factory(require('jquery'));
} else {
    factory(jQuery);
}

With this structure, we are able to trigger the plugin in different ways, attach it to different object properties if we need to or simply execute it as per normal. Here, we check if ‘define’ is a function (Require.js) and see if exports have been defined (Node.js and supporting libraries like Browserify) before falling back to regular execution. With-in each step, different methods are used to execute the factory function.

Although it’s better to have a clear understanding of how your plugin is supporting third-party libraries, these lines can still be copied and pasted into new plugins without really understanding everything they do.

If you’re interested in learning more about modular JavaScript, check out the links below.

Addy Osmani’s writing on Modular Javascript

An Introduction to Require.js

An Introduction to Browserify

Popular This Week
25 Must-Have Pages for Your E-commerce Website
January 30, 2015

25 Must-Have Pages for Your E-commerce Website

By Yvonne Weng
Taking Control of Image Loading
July 29, 2013

Taking Control of Image Loading

By Patrick Kunka
5 Ways to Highlight Shipping for a Better E-commerce User Experience
May 13, 2015

5 Ways to Highlight Shipping for a Better E-commerce User Experience

By Yvonne Weng
6 Healthy Lifestyle Brands with Blogs That Engage and Influence Customers
April 27, 2015

6 Healthy Lifestyle Brands with Blogs That Engage and Influence Customers

By Aretha Choi

Like what you’re reading? Sign up for the Barrel newsletter and receive updates.