Mark Dalgleish

Mark Dalgleish

UI Engineer - Melbourne, Australia

Testing jQuery Plugins Cross-Version With Grunt

The jQuery team have made the tough, but inevitable decision to stop supporting IE8 and below as of jQuery v2.0, while maintaining v1.9 as the backwards compatible version for the forseeable future.

In the world of modern, evergreen and mobile browsers, this was a necessary move to ensure jQuery stays relevant. Of course, this split leaves plugin authors with a bit more responsibility.

Where previously we could simply require the most recent version of jQuery, we are now likely to want to support both 1.9.x and 2.x, allowing our plugins to work everywhere from IE6 to the most bleeding edge browsers.

To facilitate this, we’ll run through the creation of a plugin using the popular JavaScript build tool, Grunt. We’ll then configure our unit tests to run automatically across multiple versions of jQuery.

A simple jQuery plugin

Note: If you have an existing plugin that doesn’t use Grunt, I’d suggest running through these steps in a clean directory and porting the resultant code into your project (with some manual tweaks, of course).

Assuming you already have Git and Node.js, we first need Grunt-init and the Grunt command line interface installed globally. Run the following command to ensure you have the latest version:

1
$ npm install -g grunt-init grunt-cli

Note: If you already have an older version of Grunt installed, you’ll need to first remove it with npm uninstall -g grunt.

We also need to install the ‘grunt-init-jquery’ template into our ’~/.grunt-init’ directory by cloning the repository:

1
git clone git@github.com:gruntjs/grunt-init-jquery.git ~/.grunt-init/jquery

We can now scaffold a new jQuery project:

1
2
3
$ mkdir jquery.plugin
$ cd jquery.plugin
$ grunt-init jquery

Once we’ve responded to all the prompts, we’re left with a basic jQuery plugin with QUnit tests.

Before we continue, we need to install our Node dependencies by running the following command from within our new plugin directory:

1
$ npm install

We can run our placeholder tests like so:

1
2
3
4
5
6
7
$ grunt qunit

  Running "qunit:files" (qunit) task
  Testing test/plugin.html....OK
  >> 5 assertions passed (51ms)

  Done, without errors.

For the purposes of this tutorial, we’re not terribly interested in the contents of the plugin. Instead, we’ll focus solely on the build and test infrastructure.

Before we make changes to our placeholder project, it’s worth having a closer look at what has been generated.

Inspecting the build

All of the configuration for our Grunt build process sits inside our Gruntfile (Gruntfile.js) in our project directory.

We have ‘qunit’ configuration, which looks for all QUnit files in the ‘test’ directory:

1
2
3
4
5
6
7
// snip...

qunit: {
  files: ['test/**/*.html']
},

// snip...

At the end of our Grunt configuration is the definition of our default task:

1
2
// Default task.
grunt.registerTask('default', ['jshint', 'qunit', 'clean', 'concat', 'uglify']);

The default task is run when the ‘grunt’ command is executed without any arguments:

1
$ grunt

Inspecting the test

The QUnit test for our plugin resides in ‘test/plugin.html’. Its default markup looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Plugin Test Suite</title>
  <!-- Load local jQuery. This can be overridden with a ?jquery=___ param. -->
  <script src="../libs/jquery-loader.js"></script>
  <!-- Load local QUnit. -->
  <link rel="stylesheet" href="../libs/qunit/qunit.css" media="screen">
  <script src="../libs/qunit/qunit.js"></script>
  <!-- Load local lib and tests. -->
  <script src="../src/plugin.js"></script>
  <script src="plugin_test.js"></script>
  <!-- Removing access to jQuery and $. But it'll still be available as _$, if
       you REALLY want to mess around with jQuery in the console. REMEMBER WE
       ARE TESTING A PLUGIN HERE, THIS HELPS ENSURE BEST PRACTICES. REALLY. -->
  <script>window._$ = jQuery.noConflict(true);</script>
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture">
    <span>lame test markup</span>
    <span>normal test markup</span>
    <span>awesome test markup</span>
  </div>
</body>
</html>

This page is responsible for including jQuery, QUnit (both JavaScript and CSS), our plugin, and any helpers required. It also provides the markup needed for QUnit to generate an HTML report.

You’ll notice, the first script file included is ’../libs/jquery-loader.js’. If we look at the contents of that file, we find this:

1
2
3
4
5
6
7
8
9
10
11
12
(function() {
  // Default to the local version.
  var path = '../libs/jquery/jquery.js';
  // Get any jquery=___ param from the query string.
  var jqversion = location.search.match(/[?&]jquery=(.*?)(?=&|$)/);
  // If a version was specified, use that version from code.jquery.com.
  if (jqversion) {
    path = 'http://code.jquery.com/jquery-' + jqversion[1] + '.js';
  }
  // This is the only time I'll ever use document.write, I promise!
  document.write('<script src="' + path + '"></script>');
}());

By including this script, we now have the ability to add ‘?jquery=X.X.X’ to the query string, when viewing this page in the browser.

Doing this will cause a hosted version of our specified version of jQuery to be included in the page rather than the default version provided inside our project.

Preparing the build

You might think that we could simply modify the QUnit file matcher in our Gruntfile to add a query string, but this won’t work. Files must exist on the file system, and query strings aren’t part of that vocabulary.

To automatically run our tests with different query strings, we first need to host our test on a local server.

Luckily, Grunt has an officially-supported ‘connect’ task which does the work for us by running a server using Connect.

To install the ‘grunt-contrib-connect’ Grunt plugin, we need to install it, and automatically save it as a development dependency in our ‘package.json’ file:

1
$ npm install --save-dev grunt-contrib-connect

Before we can use this Grunt plugin, we need to register it with Grunt by adding the following line to our Gruntfile’s ‘loadNpmTasks’:

1
grunt.loadNpmTasks('grunt-contrib-connect');

We can configure our server by adding the following task configuration to our Gruntfile:

1
2
3
4
5
6
7
connect: {
  server: {
    options: {
      port: 8085 // This is a random port, feel free to change it.
    }
  }
},

If we modify our default task to first include our newly configured ‘connect’ task, this server will start every time the default task is executed, and stopped when the build has completed:

1
2
// Default task.
grunt.registerTask('default', ['connect', jshint', 'qunit', 'clean', 'concat', 'uglify']);

Since we want to be able to test our plugin without having to concatenate and minify it, I recommend adding the following ‘test’ task:

1
grunt.registerTask('test', ['connect', 'jshint', 'qunit']);

We can now lint and test our code from the command line like so:

1
$ grunt test

Configuring the test URLs

So far we have a local Connect server running every time we trigger a build, and we have a ‘test’ task which will run the server before linting our code and running our QUnit tests.

However, you’ll find that we’re still pointing QUnit at the file system. Instead, we want it to point to our new server.

To achieve this, we’ll pass QUnit an array of URLs rather than files:

1
2
3
4
5
6
7
8
9
qunit: {
  all: {
    options: {
      urls: [
        'http://localhost:<%= connect.server.options.port %>/test/plugin.html'
      ]
    }
  }
},

Now when we run our tests, we should basically see the same result as before:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ grunt test

  Running "connect:server" (connect) task
  Starting connect web server on localhost:8085.

  Running "jshint:gruntfile" (jshint) task
  >> 1 file lint free.

  Running "jshint:src" (jshint) task
  >> 1 file lint free.

  Running "jshint:test" (jshint) task
  >> 1 file lint free.

  Running "qunit:all" (qunit) task
  Testing http://localhost:8085/test/plugin.html....OK
  >> 5 assertions passed (41ms)

  Done, without errors.

You’ll notice that this time, QUnit is accessing a URL instead of a file. This means that we’re now free to add query strings to our URLs, allowing us to automate testing across multiple versions of jQuery with ease:

1
2
3
4
5
6
7
8
9
10
qunit: {
  all: {
    options: {
      urls: [
        'http://localhost:<%= connect.server.options.port %>/test/plugin.html?jquery=1.9.0',
        'http://localhost:<%= connect.server.options.port %>/test/plugin.html?jquery=2.0.0b1'
      ]
    }
  }
},

Since there will be a lot of repetition in the URLs, let’s clean it up with use of the Array prototype’s ‘map’ method:

1
2
3
4
5
6
7
8
9
qunit: {
  all: {
    options: {
      urls: ['1.9.0', '2.0.0b1'].map(function(version) {
        return 'http://localhost:<%= connect.server.options.port %>/test/plugin.html?jquery=' + version;
      })
    }
  }
},

If we run our tests, you’ll see multiple URLs have been loaded, and twice as many assertions have passed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ grunt test

  Running "connect:server" (connect) task
  Starting connect web server on localhost:8085.

  Running "jshint:gruntfile" (jshint) task
  >> 1 file lint free.

  Running "jshint:src" (jshint) task
  >> 1 file lint free.

  Running "jshint:test" (jshint) task
  >> 1 file lint free.

  Running "qunit:all" (qunit) task
  Testing http://localhost:8085/test/plugin.html?jquery=1.9.0....OK
  Testing http://localhost:8085/test/plugin.html?jquery=2.0.0b1....OK
  >> 10 assertions passed (98ms)

  Done, without errors.

Making it bulletproof

By default, this setup loads each version directly from the jQuery site. If you’re anything like me, you sometimes develop with little to no internet connectivity, and this limitation would prevent you from running the full suite.

It’s a good idea to add each major supported version of jQuery to your ‘lib/jquery’ directory (with a ‘jquery-x.x.x’ naming convention), and modify ‘libs/jquery-loader.js’ to load these local copies instead:

1
2
3
4
5
6
7
8
9
10
11
12
(function() {
  // Default to the local version.
  var path = '../libs/jquery/jquery.js';
  // Get any jquery=___ param from the query string.
  var jqversion = location.search.match(/[?&]jquery=(.*?)(?=&|$)/);
  // If a version was specified, use that version from code.jquery.com.
  if (jqversion) {
    path = '../libs/jquery/jquery-' + jqversion[1] + '.js';
  }
  // This is the only time I'll ever use document.write, I promise!
  document.write('<script src="' + path + '"></script>');
}());

Testing in the cloud

As always, it’s a great idea to automatically run these tests after every push to GitHub, or on every pull request that is sent to you. To achieve this, we can leverage Travis CI with only a couple of changes to our project.

First add the ‘.travis.yml’ configuration file to your plugin’s base directory:

1
2
3
4
language: node_js

node_js:
  - 0.8

Then, set the ‘npm test’ script in your ‘package.json’ file to run our new Grunt ‘test’ task:

1
2
3
4
5
6
7
// Snip...

"scripts": {
  "test": "grunt test"
},

// Snip...

Finally, follow the official Travis CI guide to create an account, if needed, and activate the GitHub service hook. Once completed, you’ll have the confidence of knowing that the downloadable version of your plugin can’t be broken by mistake.

Keeping it in check

Now that we have a framework for testing multiple versions, it’s worth testing the minimum jQuery version your plugin supports, and each major version above it.

At a minimum, I’d recommend testing in 1.9.x and 2.x to ensure that any differences between the two versions don’t inadvertently break your plugin. Since both versions will be developed in parallel as long as old versions of IE maintain significant market share, it’s the least we can do for our users.

Update (19 Feb 2013): This article now reflects changes made in Grunt v0.4

A Touch of Class: Inheritance in JavaScript

The object-oriented features of JavaScript, such as constructors and prototype chains, are possibly the most misunderstood aspects of the language. Plenty of people who have been working full time with JavaScript still struggle with these concepts, and I argue that this is purely a result of its confusing, Java-style syntax.

But it doesn’t have to be this way. Hiding beneath the “new” keyword is a rich and elegant object model, and we’ll spend some time coming to grips with it.

By seeing how simple inheritance in JavaScript can be if we use more modern syntax, we’ll be able to better understand the unfortunate syntax we’ve been stuck with from the beginning.

JavaScript’s lack of class

The biggest act of misdirection in JavaScript is that it looks like it has classes. When someone is new to JavaScript, particularly if they come from another language that does have classes, they might be surprised to find code like this:

1
var charlie = new Actor();

Which leads to quite possibly the biggest surprise people face when trying to come to grips with JavaScript: it doesn’t have classes. You might assume that elsewhere in the code is the class definition for “Actor”, but instead you find something like this:

1
2
3
4
5
6
7
function Actor(name) {
    this.name = name;
}

Actor.prototype.act = function(line) {
    alert(this.name + ': ' + line);
};

From here it only gets worse when multiple inheritance is involved:

1
2
3
4
5
6
7
8
9
10
function Actor(name) {
    this.name = name;
}
Actor.prototype.canSpeak = true;

function SilentActor() {
    Actor.apply(this, arguments);
}
SilentActor.prototype = new Actor();
SilentActor.prototype.canSpeak = false;

Sometimes despite years of experience, even those who realise that JavaScript doesn’t have classes may still find themselves never quite grasping the concepts behind such cryptic syntax.

Taking a step back

To put these decisions in the proper historical context, we need to take a trip back to the year 1995. Java was the hot new web language, and Java applets were going to take over the world.

We all know how this story ends, but this was the environment in which Brendan Eich was toiling away at Netscape on his “little” web scripting langauge.

This cultural pressure resulted in some rebranding. What was originally “Mocha”, then “LiveScript”, eventually became known as “JavaScript”. While it may have eventually extended beyond its original scope, Brendan Eich wasn’t shy about pitching it as Java’s little brother.

It’s just a prototype

The problem, of course, is that despite JavaScript’s syntactic similarities to Java, its conceptual roots lay elsewhere. A dynamically typed language, it borrowed functional aspects from Scheme, and prototypal inheritance from Self.

In classical languages, like Java, instances are created from classes. JavaScript differs in that it has prototypal inheritance, in which there are no classes.

Instead, objects inherit from other objects.

Modern inheritance

To best understand the concepts behind prototypal inheritance, you must unlearn what you have learned. You’ll find it is much easier to appreciate object-to-object inheritance if you pretend you’ve never seen JavaScript’s misleading, classically-styled syntax.

In ECMAScript 5, the latest version of JavaScript available in all modern browsers (Chrome, Firefox, Safari, Opera, IE9+), we have new syntax for creating object-to-object inheritance, based on Douglas Crockford’s original utility method for simple inheritance:

1
var childObject = Object.create(parentObject);

If we use Object.create to recreate our ‘SilentActor’ example from earlier, it becomes much easier to see what’s really going on:

1
2
3
4
5
6
7
8
9
10
11
12
// Our 'actor' object has some properties...
var actor = {
  canAct: true,
  canSpeak: true
};

// 'silentActor' inherits from 'actor'
var silentActor = Object.create(actor);
silentActor.canSpeak = false;

// 'busterKeaton' inherits from 'silentActor'
var busterKeaton = Object.create(silentActor);

In modern browsers, we also have a new method for reliably inspecting the prototype chain:

1
2
3
Object.getPrototypeOf(busterKeaton); // silentActor
Object.getPrototypeOf(silentActor); // actor
Object.getPrototypeOf(actor); // Object

In this simple example, we’ve been able to set up a prototype chain without using a single constructor or “new” keyword.

Walking the chain

So how does a prototype chain work? When we try to read a property from our new ‘busterKeaton’ object, it checks the object itself and, if it didn’t find the property, it traverses up the prototype chain, checking each of its prototype objects in order until it finds the first occurence of the property.

All this happens when we ask for the value of a property from an object.

1
busterKeaton.canAct;

In order to properly evaluate the value of ‘canAct’ on ‘busterKeaton’, the JS engine does the following:

  1. Checks ‘busterKeaton’ for the ‘canAct’ property, but finds nothing.
  2. Checks ‘silentActor’, but doesn’t find the ‘canAct’ property.
  3. Checks ‘actor’ and finds the ‘canAct’ property, so returns its value, which is ‘true’.

Modifying the chain

The interesting thing is that the ‘actor’ and ‘silentActor’ objects are still live in the system and can be modified at runtime.

So, for a contrived example, if all silect actors lost their jobs, we could do the following:

1
2
3
4
silentActor.isEmployed = false;

// So now...
busterKeaton.isEmployed; // false

Where’s “super”?

In classical languages, when overriding methods, you can run the method from the parent class in the current context.

In JavaScript we have even more options: we can run any function in any context.

How do we achieve this? Using JavaScript’s ‘call’ and ‘apply’ methods which are available on all functions. Appropiately enough, these are available because they exist on the ‘Function’ prototype.

They both allow us to run a function in a specific context which we provide as the first parameter, along with any arguments we wish to pass to the function.

Using our ‘silentActor’ example, if we want to achieve the equivalent of calling ‘super’, it looks something like this:

1
2
3
4
5
6
7
8
actor.act = function(line) {
    alert(this.name + ': ' + line);
}

silentActor.act = function(line) {
    // Super:
    actor.act.call(this, line);
};

If it took multiple arguments, it would look like this:

1
2
3
4
5
6
7
silentActor.act = function(line, emotion) {
    // Using 'call':
    actor.act.call(this, line, emotion);

    // Using 'apply':
    actor.act.apply(this, [line, emotion]);
};

Where are the constructors?

With this setup, it is quite simple for us to create our own DIY constructor:

1
2
3
4
5
6
7
8
9
10
function makeActor(name) {
    // Create a new instance that inherits from 'actor':
    var a = Object.create(actor);

    // Set the properties of our instance:
    a.name = name;

    // Return the new instance:
    return a;
}

Understanding prototypes with a touch of class

Now that we’ve seen how simple object-to-object inheritance can be, it’s time to look at our original example again with fresh eyes:

1
var charlie = new Actor();

‘Actor’ is, obviously, not a class. It is, however, a function.

It could be a function that does nothing:

1
function Actor() {}

Or, it could set some properties on the new instance:

1
2
3
function Actor(name) {
    this.name = name;
}

In our example, the ‘Actor’ function is being used as a constructor since it is invoked with the “new” keyword.

The funny thing about JavaScript is that any function can be used as a constructor, so there’s nothing special about our ‘Actor’ function that makes it different from any other function.

Clarifying “constructors”

Since any function can be a constructor, all functions have a ‘prototype’ property just in case they’re used as a constructor. Even when it doesn’t make sense.

A perfect example is the ‘alert’ function, which is provided by the browser. Even though it’s not meant to be used as a constructor (in fact, the browser won’t even let you), it still has a ‘prototype’ property:

1
2
3
typeof alert.prototype; // 'object'

new alert(); // TypeError: Illegal invocation

When ‘Actor’ is used as a constructor, our new ‘charlie’ object inherits from the object sitting at ‘Actor.prototype’.

Functions are objects

If you missed that, let me re-iterate. Functions are objects, and can have arbitrary properties.

For example, this is completely valid:

1
2
3
4
5
function Actor(){}

// We can set any property:
Actor.foo = 'bar';
Actor.abc = [1,2,3];

However, the ‘prototype’ property of a function is where we store the object that all new instances will inherit from:

1
2
3
4
5
6
7
function Actor(){}

// Used for constructors:
Actor.prototype = {foo: 'bar'};

var charlie = new Actor();
charile.foo; // 'bar'

Creating the chain

Setting up multiple inheritance with our class-like syntax should now be a bit easier to grasp.

It’s simply a case of making a function’s ‘prototype’ property inherit from another function’s ‘prototype’:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Set up Actor
function Actor() {}
Actor.prototype.canAct = true;

// Set up SilentActor to inherit from Actor:
function SilentActor() {}
SilentActor.prototype = Object.create(Actor.prototype);

// We can now add new properties to the SilentActor prototype:
SilentActor.prototype.canSpeak = false;

// So instances can act, but can't speak:
var charlie = new SilentActor();
charlie.canAct; // true
charlie.canSpeak; // false

Construction: Just 3 simple steps

At this point, it’s important to point out what’s going on inside a constructor. Instantiating a new object with a “constructor” performs three actions and, as an example, let’s take a look at what happens with our ‘Actor’ constructor:

1
var charlie = new Actor();

This one line of the code does the following:

  1. Creates a new object, ‘charlie’, that inherits from the object sitting at ‘Actor.prototype’,
  2. Runs the ‘Actor’ function against ‘charlie’, and
  3. Returns ‘charlie’.

Coming full circle

You might think that this sounds awfully familiar because this precisely mirrors the steps in our “DIY constructor” from earlier, which looked like this:

1
2
3
4
5
6
7
8
9
10
function makeActor(name) {
    // Create a new instance that inherits from 'actor':
    var a = Object.create(actor);

    // Set the properties of our instance:
    a.name = name;

    // Return the new instance:
    return a;
}

Just like our earlier example, asking our ‘busterKeaton’ object for the ‘canAct’ property will walk up the prototype chain, except this time the JS engine will act slightly differently:

  1. Checks ‘busterKeaton’ for the ‘canAct’ property, but finds nothing.
  2. Checks ‘SilentActor.prototype’, but doesn’t find the ‘canAct’ property.
  3. Checks ‘Actor.prototype’ and finds the ‘canAct’ property, so returns its value.

You’ll notice that the chain now consists of objects sitting at the ‘prototype’ property of functions, each of which had been used as the object’s constructor.

Keeping it classic

Even after understanding the syntax, it’s still common for people to want to get as far away from it as possible.

There are implementations of “classical” inheritance built on top of JavaScript’s prototypal inhertance, the most notable of which is John Resig’s famous “Class” function.

You’ll also find a lot of languages that compile to JavaScript, such as CoffeeScript or TypeScript, offer standard class syntax. TypeScript in particular ensures it closely resembles the draft ECMAScript 6 class specification.

Yes, the ES6 specification draft contains “class” sugar which, like CoffeeScript and TypeScript, is really prototypal inheritance under the hood.

Clearing the air

Of course none of the classical misdirection and shim layers can really replace a true understanding of prototypal inheritance and the power it affords you.

Hopefully this article has helped clear things up for you and, if you still find yourself puzzled, I hope at the very least that the next time you dig into prototypal inheritance, your mind will have been sufficiently prepared for thinking like Brendan Eich did all those years ago.

If not, there’s always Java applets.

Slides from Web Directions South 2012

This article is based on a presentation I gave on 18 October, 2012 at Web Directions South in Sydney, Australia. The slides are available online.

Mobile Parallax With Stellar.js

Parallax effects can be an extremely effective way to engage users during the simple act of scrolling. What is normally a simple process of moving static content along the screen can become an act of moving through a makeshift, HTML world. While the effect can certainly be abused, when applied with a light, elegant touch, it can breathe new life into your site.

Unfortunately this effect is hampered on touch devices, where tactile feedback and intertial scrolling would perfectly suit parallax animation. Mobile browsers place a limit on script execution during scroll, so your carefully crafted parallax effects won’t happen until scrolling has come to a complete halt. As you can imagine, this is far from impressive.

As a personal goal to not only make parallax effects as simple as possible, but to make them work on mobile browsers where these effects are notoriously difficult, I created a scrolling parallax library called Stellar.js.

If you’re feeling impatient, you can skip straight to the demo. At this point, you might be wondering what the issue with mobile browsers happens to be, and how Stellar.js lets us overcome this.

The overflow problem

When we got our first taste of HTML5-capable mobile browsers, one of the first things web designers and developers noticed was their lack of scrolling overflow support.

On desktop we took it for granted that we could add a scroll bar to any element. This ability was taken away from us on these new platforms which, inside their own native applications, commonly made use of small, nested scrolling panes in order to better fit more content on such relatively tiny screens.

Plenty of people noticed this shortcoming, and some library authors weren’t ready to be defeated.

Enter touch scrolling libraries

It didn’t take long for some smart developers to find ways around this limitation by creating libraries which emulate native scroll using CSS3 transforms.

While their need is somewhat dimished with the overflow-scrolling property, they still prove vital for overcoming our JavaScript issues. By emulating native scroll, the browser no longer throttles our script execution.

There’s quite a few to choose from, and all of these will integrate with Stellar.js:

Before we use one of these libraries, we first need to create a standard, non-mobile parallax example.

Marking up a simple desktop-only demo

To get started, we need a basic HTML page with jQuery, Stellar.js and some parallax elements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!doctype html>
<html>
  <head>
      <title>Mobile Parallax with Stellar.js - Demo</title>
      <link rel="stylesheet" type="text/css" href="style.css" />
  </head>
  <body>

      <h1>Stellar.js</h1>
      <h2>Mobile Parallax Demo</h2>
      <p>Scroll down to see Stellar.js in action.</p>
              
      <section>
          <div class="pixel" data-stellar-ratio="1.1"></div>
          <div class="pixel" data-stellar-ratio="1.2"></div>
          <div class="pixel" data-stellar-ratio="1.3"></div>
          <div class="pixel" data-stellar-ratio="1.4"></div>
          <div class="pixel" data-stellar-ratio="1.5"></div>
          <div class="pixel" data-stellar-ratio="1.6"></div>
          <div class="pixel" data-stellar-ratio="1.7"></div>
          <div class="pixel" data-stellar-ratio="1.8"></div>
          <div class="pixel" data-stellar-ratio="1.9"></div>
          <div class="pixel" data-stellar-ratio="2.0"></div>
      </section>

      <script src="jquery.min.js"></script>
      <script src="jquery.stellar.min.js"></script>
      <script src="script.js"></script>
  </body>
</html>

A couple of files are referenced here which haven’t been created yet: “script.js” and “style.css”.

Scripting and styling

The code in script.js initialises Stellar.js, which activates all elements with data-stellar-ratio data attributes:

1
2
3
4
5
6
$(function(){
  $.stellar({
    horizontalScrolling: false,
    verticalOffset: 150
  });
});

In style.css we specify how the content should look, paying particular attention to the styling of our “pixel” elements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
* {
  margin: 0;
  padding: 0;
}

body {
  background: #2491F7;
}

section {
  position: relative;
  top: 400px;
  width: 300px;
  height: 3000px;
  margin: 0 auto;
}

h1, h2, p {
  color: white;
  font-family: helvetica, arial;
  text-shadow: 0 1px 2px rgba(0,0,0,0.5);
  position: absolute;
  left: 50%;
  width: 300px;
  margin-left: -150px;
}

h1 {
  font-size: 68px;
  top: 85px;
}

h2 {
  font-size: 27px;
  top: 170px;
}

p {
  font-size: 17px;
  top: 210px;
}

.pixel {
  position: absolute;
  width: 15px;
  height: 15px;
  border-radius: 15px;
  background: white;
}

  .pixel:nth-child(2)  { left: 30px;  }
  .pixel:nth-child(3)  { left: 60px;  }
  .pixel:nth-child(4)  { left: 90px;  }
  .pixel:nth-child(5)  { left: 120px; }
  .pixel:nth-child(6)  { left: 150px; }
  .pixel:nth-child(7)  { left: 180px; }
  .pixel:nth-child(8)  { left: 210px; }
  .pixel:nth-child(9)  { left: 240px; }
  .pixel:nth-child(10) { left: 270px; }

We now have a very basic, desktop-only parallax demo but, as you’d expect, we are going to make this work cross-platform.

Adding mobile support

We first need to make some adjustments to the markup. We need to add a mobile-friendly meta tag:

1
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;" />

We next need to add a couple of divs for iScroll, namely a “wrapper” div and a “scroller” div:

1
2
3
4
5
6
7
<div id="wrapper">
  <div id="scroller">

    <section>...</section>

  </div>
</div>

Finally, we need to include the JavaScript for iScroll:

1
<script src="iscroll.js"></script>

The complete HTML file now looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!doctype html>
<html>
  <head>
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;" />
    <title>Mobile Parallax with Stellar.js - Demo</title>
    <link rel="stylesheet" type="text/css" href="style.css" />
  </head>
  <body>

    <div id="wrapper">
      <div id="scroller">

        <h1>Stellar.js</h1>
        <h2>Mobile Parallax Demo</h2>
        <p>Scroll down to see Stellar.js in action.</p>

        <section>
          <div class="pixel" data-stellar-ratio="1.1"></div>
          <div class="pixel" data-stellar-ratio="1.2"></div>
          <div class="pixel" data-stellar-ratio="1.3"></div>
          <div class="pixel" data-stellar-ratio="1.4"></div>
          <div class="pixel" data-stellar-ratio="1.5"></div>
          <div class="pixel" data-stellar-ratio="1.6"></div>
          <div class="pixel" data-stellar-ratio="1.7"></div>
          <div class="pixel" data-stellar-ratio="1.8"></div>
          <div class="pixel" data-stellar-ratio="1.9"></div>
          <div class="pixel" data-stellar-ratio="2.0"></div>
        </section>

      </div>
    </div>

    <script src="jquery.min.js"></script>
    <script src="jquery.stellar.min.js"></script>
    <script src="iscroll.js"></script>
    <script src="script.js"></script>
  </body>
</html>

Detecting mobile devices

Now that we have iScroll available, and the required markup, we need to update our script.js file.

We’re going to be introducing some local variables that we don’t want leaking into the global scope, so our first step is to wrap our code in an immediately invoked function expression (IIFE):

1
2
3
(function(){
  // code goes here...
})();

In order to activate iScroll only when we’re on a mobile WebKit browser, we need to do some dreaded user agent sniffing:

1
2
var ua = navigator.userAgent,
  isMobileWebkit = /WebKit/.test(ua) && /Mobile/.test(ua);

In order to style the mobile version differently, we can now use our isMobileWebkit flag to add a class to our HTML element:

1
2
3
if (isMobileWebkit) {
  $('html').addClass('mobile');
}

Finally, we need to initalise iScroll if we’re on mobile:

1
2
3
4
var iScrollInstance;
if (isMobileWebkit) {
  iScrollInstance = new iScroll('scroller');
}

Now we can initalise Stellar.js differently whether we’re on desktop or mobile.

Understanding Stellar.js on mobile

Before we continue, it’s important to try to understand how Stellar.js is able to work with a touch scrolling library like iScroll.

Stellar.js has the concept of scroll properties which are adapters that tell it how to read the scroll position. The default scroll property is, appropriately enough, “scroll”, which covers the standard onscroll style animation.

When using Stellar.js’ default settings, it’s equivalent to writing this code:

1
2
3
$(window).stellar({
  scrollProperty: 'scroll'
});

Here you can see that we’ve specified that “window” is the source of our scrolling, and we read its scroll position using the “scroll” scroll property adapter.

Once we use iScroll, there is no longer a scroll position in the traditional sense. Native scroll is being simulated by moving the “scroller” div inside the “wrapper” div using CSS3 transforms.

Thankfully, Stellar.js has a “transform” adapter to cover this exact case. All we have to do is point Stellar.js at the div being moved, and specify which “scrollProperty” to use:

1
2
3
$('#scroller').stellar({
  scrollProperty: 'transform'
});

Performant positioning in Mobile WebKit

Repositioning elements rapidly is fairly demanding on mobile devices. In order to achieve a seamless effect, we need to ensure that our parallax effects are hardware accelerated.

In WebKit, hardware acceleration is triggered by using “translate3d”. Stellar.js provides support for this method of positioning via the “transform” position property adapter:

1
2
3
4
$('#scroller').stellar({
  scrollProperty: 'transform',
  positionProperty: 'transform'
});

By using this adapter, our parallax effects should now be as smooth as possible.

Hooking Stellar.js up to iScroll

Now that we’ve seen all the individual pieces needed to make our JavaScript work, it’s time to see the final script.js in its entirety:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(function(){
  var ua = navigator.userAgent,
    isMobileWebkit = /WebKit/.test(ua) && /Mobile/.test(ua);

  if (isMobileWebkit) {
    $('html').addClass('mobile');
  }

  $(function(){
    var iScrollInstance;

    if (isMobileWebkit) {
      iScrollInstance = new iScroll('wrapper');

      $('#scroller').stellar({
        scrollProperty: 'transform',
        positionProperty: 'transform',
        horizontalScrolling: false,
        verticalOffset: 150
      });
    } else {
      $.stellar({
        horizontalScrolling: false,
        verticalOffset: 150
      });
    }
  });

})();

Of course, this won’t work until we’ve updated our style sheet.

Styling for mobile

Finally, we add our mobile-specific styles which act on the body tag and our new iScroll containers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.mobile body {
  overflow: hidden;
}

.mobile #wrapper {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 100%;
}

.mobile #scroller {
  height: 3000px;
}

Seeing the end result

If you take a look at the final page, you’ll see we now have a page with scrolling parallax which works on desktop and mobile.

From here we have a base to play with our parallax site, while ensuring it works correctly cross-platform. At this point, if you haven’t already, it’s a good idea to read through the Stellar.js documentation to get a better idea of the control you have over the effect.

Have you made a cross-platform parallax site with Stellar.js? Let me know on Twitter, or post a link in the comments.

Update (27 Jan 2013): This article now reflects changes made in Stellar.js v0.6