This post is about making your web page perform better using a real world example. As you know, we recently launched a very cool animated comic on A/B Testing. It is scroll animation describing what is A/B testing. I’ll talk about it as an example and walk you through its performance issues, how we debugged them and finally what we did to extract 60 FPS out of it.

The process we see in following text will applies more or less to all web pages in general. Here’s what you need to get started:

  1. A janky web page.
  2. Google Chrome with its awesome devtools.
  3. Determination to make it run as smooth as a hot knife through butter :)

Worry not if you are missing any of the above, you can still read on. Let us begin.

WHAT is causing the issue?

All we know now is that our page is janky. When you scroll up/down you’ll notice that the animation is quite choppy. There are sudden jumps occasionally while scrolling which is really irritating and obviously a bad user experience. We don’t know what is causing this. The very first step we take here is profile the page using Chrome devtool’s Timeline feature. So I went on and fired up my devtools.

Open the devtools

Chrome devtools

Devtools in chrome can be fired either going to Tools > Developer Tools or using the shortcut Ctrl + Shift + I on Windows/Linux and Cmd + Opt + I on Mac.

Select frames tab

Frames tab

Frames tab basically will let us visualize each frame individually showing how much time was taken by that frame and for what tasks.

Filter out events taking more than 15ms

Chrome devtools

Note that we are targeting 60 FPS here. A little math here gives us the number 16.666 ms (1 / 60 * 1000). This is the time budget available per frame to do its thing if we want a consistent 60 FPS.

Therefore, we essentially want to investigate those frames which are crossing this time limit. To do so, select the >= 15ms option from bottom bar as shown.

Record

Chrome devtools

Press the ‘Record’ button at the bottom to start devtools record what’s happening on the page. Once you do that, go back to the page and interact with the page as one would normally do exposing the issues we are trying to debug.

In my case, the page was feeling choppy while scrolling between slides. So I simply kept scrolling on the page like a normal user. After interacting for a while with the page, I get back to the devtools window and press the same button to stop the recording.

Notice the frames

Chrome devtools

You now see the frame data for your page… something like in the snapshot above. In the image you’ll notice a vertical limit with the label 60 FPS just below the label for 30 FPS. These limits are for the frames under which they need to do their stuff if the respective framerate is to be achieved. Once you know this, you’ll straight away conclude that almost all of our frames our crossing that limit like hell! This is the point where we have actually visualized and confirmed the issue. Lets find out the cause.

Script events taking more than 15ms

Chrome devtools

Every frame’s bar is made of different colour components. In the above snapshots we see only yellow and green ones. A quick look at the color legend in the bottom bar tells us that yellow is script time and green is painting. A closer analysis tells us that most frames are in majority made up of yellow component. This means that most of the frame’s time is spent in executing script.

Moreover if you hover over any small horizontal yellow bars below, as show in the snapshot above, you’ll also see the exact time that our scripts are taking per frame along with the corresponding event that triggered it. In my case, it’s the scroll event (we expected that…no?). Some of those scroll events are taking upto 27 ms which is much much more than our budget of 16ms per frame.

Issue detected: Scroll event script

After all this analysis using the devtools we hence come to the conclusion that it’s the script executing for every scroll event that is the cause of issue here. Next step in our debug process is finding WHY it is causing it.

WHY is it causing an issue?

Let’s investigate the code

Our code for the callback bound to the Scroll event is as follows:

$(window).scroll(function() {
    var currentScroll = $(this).scrollTop();

    // Set the position to current slide if the user scrolls manually.
    checkpoints.forEach(function(checkpoint, index) {
      if(currentScroll <= checkpoints[index] && currentScroll > checkpoints[index - 1])
        i = index;

      if(currentScroll < checkpoints[1])
        i = 0;

      if(i == checkpoints.length - 1) {
        $("#main_form, .social-icons").css("visibility", "visible");

        $("a#scrollDown").fadeOut();
        $("a#autoscroll").fadeOut();
      }
      else {
        $("#main_form, .social-icons").css("visibility", "hidden");

        $("a#scrollDown").fadeIn();
        $("a#autoscroll").fadeIn();
      }

      if(currentScroll > 0)
        $("a#scrollUp").fadeIn();
      else
        $("a#scrollUp").fadeOut();
    });
});

This callback function will be our target from now on.

Scroll event is too frequent to handle scripts taking time

First thing that striked me was that the Scroll event is fired too frequently. Every time you scroll on a page, that event is fired multiple times within seconds. Therefore any code that is attached to the Scroll event will be fired with the same frequency. And if that code is computation heavy, we are done!

FIX

To improve the situation here, we have 2 ways:

A. Make the Scroll event fire less frequently B. Optimize the callback’s code to take less execution time

FIX A. Make the Scroll event fire less frequently

I could make the Scroll code fire less frequently in our case as it did not had any usability hit. In fact mostly the code thats required to be executed on Scroll event can be run on little longer intervals without any user experience loss.

This thing was easy to do. Ben Alman has an awesome jQuery plugin written for throttling/debouncing functions. Its very easy to use too. Simply get the plugin into your page and pass the throttled function to Scroll event like so:

var callback = function () {
  ...
}

$(window).scroll( $.throttle(350, callback));

As you see in above code, I have made my callback to fire atmost once within 350 ms. In other words, there will be atleast an interval of 350 ms between 2 calls to that function. This should probably keep those adjacent long yellow bar at some distant from each. We’ll see.

TEST!

We made a small change from our side. But remember, there is no point of it without actually testing the page and getting a performance boost. So lets repeat the profiling procedure again.

Here is what we got this time:

Result

Seems to have worked quite a bit! We have lesser frames overshooting the 16ms budget.

FIX B. Optimize the callback’s code to take less execution time

Secondly, its also important to optimize the code inside that callback at that is what is causing the frames to go beyond our 16ms budget.

If you look closely inside the callback’s code and have a basic understanding of what not do while jQuery, you’ll see some horrible things happening there. I’ll not go in much details on why those things are bad as our focus is on using devtools in this article. Lets list out what all jQuery menace we see in it:

  • Cache jQuery objects

At many places, jQuery is being used to reference element by passing their selectors again and again in the callback. That is BAD. Unless these references will change in future, its wise to calculate them once and cache for future use.

Some of the lines where jQuery is being used unnecessarily:

  var currentScroll = $(this).scrollTop(); // this is always window object
  $("#main_form, .social-icons").css("visibility", "visible");

  $("a#scrollDown").fadeOut();
  $("a#autoscroll").fadeOut();
  $("a#scrollUp").fadeIn();
  $("a#scrollUp").fadeOut();
  • Unnecessary animation

Have a look at the following code snippet:

if(i == checkpoints.length - 1) {
  socialIcons.css("visibility", "visible");

  scrollDownBtn.fadeOut();
  scrollAutoBtn.fadeOut();
}
else {
  socialIcons.css("visibility", "hidden");

  scrollDownBtn.fadeIn();
  scrollAutoBtn.fadeIn();
}

The first if checks if we are on the last iteration of the loop or not. If not, then the else part executes. Which means if the loop runs 100 times, 99 times the else part executes. Moreover if you see carefully the code in the else block, it will keep fading in/out certain elements on each iteration, even when it has done the same thing in past iteration. Taking account the heavy animation account cost in jQuery, this is absolutely unnecessary work being done here.

We could simply do that stuff once and set a flag which will be checked next time and we only do it again if the flag is unset somehow.

Final code

After the above 2 fixes, here is how our Scroll event callback looks like:

function scrollHandling() {
  var currentScroll = $window.scrollTop();

  // Set the position to current slide if the user scrolls manually.
  checkpoints.forEach(function(checkpoint, index) {
    if(currentScroll <= checkpoints[index] && currentScroll > checkpoints[index - 1])
      i = index;

    if(currentScroll < checkpoints[1])
      i = 0;

    if(i == checkpoints.length - 1) {
      socialIcons.css("visibility", "visible");

      scrollDownBtn.fadeOut();
      scrollAutoBtn.fadeOut();
      lastSlideUIapplied = true;
    }
    else if (lastSlideUIapplied) {
      socialIcons.css("visibility", "hidden");

      scrollDownBtn.fadeIn();
      scrollAutoBtn.fadeIn();
      lastSlideUIapplied = false;
    }

    if (currentScroll > 0) {
      scrollUpBtn[0].style.display == 'none' && scrollUpBtn.fadeIn();
    }
    else {
      scrollUpBtn.fadeOut();
    }
  });
}

$window.scroll( $.throttle(250, scrollHandling));

TEST AGAIN!

Needless to say, our next step is to test the changes made. Here is what the timeline says now:

Final result

Bingo!

  • We hardly have any frames overshooting the target line of 60 FPS.
  • We get an average execute time of 11.71 ms per frame with a standard deviation of around 4.97 ms.

Going further

We still see paint (green) events which are causing some frames to overshoot the border. It is basically on slides where large image are being animated on the screen. Its not that we can scale down the images or stop them from being painted. The solution still needs to be figured out to optimize the painting going on here. Suggestions?

Last words

As Chrome folks say it, don’t guess it, test it!


In one of our previous posts, we talked about the problems we faced when communicating with frames on a different domain in our application Visual Website Optimizer, and highlighted the possible solutions to each of those problems.

We are proud to announce please.js, a Request/Response based cross-domain communication. If you’ve ever faced problems in cross-domain frame communication, fear not - just say please!

please.js on Github

What is please.js

please.js is a Request/Response based wrapper around the PostMessage API that makes use of jQuery Promises. Here’s a quick example to load an iframe window’s location:

var frameWindow = $('iframe').get(0).contentWindow;

please(frameWindow).call('window.location.reload');

please.js is based on top of jQuery and the jQuery Promise API. jQuery version 1.6 or above is preferred. To make the communication between two windows on different domains work, both of them must be injected with the same version of jQuery and please.js.

Currently, please.js is an alpha release (0.1.0). Down the line, we would like to add features like support for communication in Chrome Extensions and improving the documentation to make it easier for all users to get started easily.

How it works

The underlying concept is simple. Two frames need to communicate with each other asynchronously. To access one of the child frames on a page, the parent frame sends a please.Request to the child frame. The Request object is a lot like the request sent by the browser to a server. It contains information on what needs to be done in the child frame (call a function, get/set a property or a variable, or access a DOM node using jQuery). The child frame sends a please.Response back to the parent frame with the result of what the parent frame asked. For a function call request, it is the return value of that function, and for a get request, the value of the variable/property is returned.

Contributing

If you would like to contribute, you can submit an issue on GitHub. Will be great if accompanied by a failing test case and/or a pull request.


Recently, we launched our first ever animated guide to A/B testing which made it to the top of HN homepage (Yay!).

In this post, I’ll go through the process of how I created the page using HTML5 and JS. Let’s get started!

Setting up things

I searched about some existing parallax scrolling JS scripts and came across Skrollr.js which made my work a piece of cake! If you are going to create your own parallax scrolling page, then I would recommend you to use this library. Apart from that, I also used scrollTo.js and mousewheel.js for scroll handling.

Also, I wanted to make the images used in that page look sharp on retina screens so I used a little LESS mixin from RetinaJS to make sure that retina screens show the images @2x.

Getting started

After looking at some examples of Skrollr, I was ready to start building up the page. The best thing about Skrollr is that it automatically set things up for you and also handles the parallax scrolling on mobile devices.

Now, I saved two versions (1x and 2x, for retina) of all the images and searched for a good comic font. Each slide on that page is a mixture of some text and image elements. I gave each slide an absolute positioning and 100% width and height. Also, each element in the slides are fixed positioned are made to appear and disappear using the opacity property. Here’s the code for the first slide:

  <!-- Slide 1 -->
  <div class=slide id=slide1>
    <div class=bob
      data-0=left: 0%; opacity:0;
      data-1000=left: 50%; opacity:1;
      data-3600=left: 50%; opacity:1;
      data-4800=left: 50%; opacity:0;>
    </div>

    <div class=text
      data-1200=opacity:0; bottom:0%; margin-bottom: 0 
      data-2400=opacity:1; bottom:50%; margin-bottom: -46px
      data-3600=opacity:1; bottom:50%; margin-bottom: -46px; right: 50%
      data-4800=opacity: 0; bottom: 50%; margin-bottom: -48px; right: 0%>

      Meet <strong>Bob</strong>
    </div>
  </div>

The only thing that Skrollr needs is the data-px attribute with some CSS properties passed in that attribute. Here, Bob will be at 0% left having 0 opacity at the start. Now if the user scrolls to 1000px, s/he would see Bob’s image appearing from left to the center with increasing opacity. Thats how it works, you just need to time your animations in terms of pixels and Skrollr will handle it for you. Here, both bob and text are fixed positioned. To make things responsive, I first positioned everything to center using this:

  .element {
    width: 100px; height: 100px;
    left: 50%; top: 50%;
    margin-left: -50px; 
    margin-top: -50px;
  }

After this, I altered the margins to position it perfectly so that on any resolution it will start from the center. I did the same thing for all the elements in each slide. Most of the elements are animated using CSS3 transforms while others are just faded in and out using opacity property.

Scroll handling

All this completed 80% of the page. Now, the only thing left was the scroll handling. I had to make sure that on each scroll, a slide should finish the animation properly and should not be left in between. To do this, I created checkpoints of the scroll position where each slide starts/ends. Now on each scroll, I incremented/decremented a counter based on the scroll direction. Based on that counter’s current value the page is scrolled to the position from the checkpoints array and any other scroll event is ignored in that duration. Here’s the code for this:

  var i = 0;
  var checkpoints = [0, 3600, 6000, 11200, 14800, 17200];
  var timer = [0, 1000, 1000, 1500, 1500, 1500];

  function scrollDown() {
    if(i < checkpoints.length - 1 && percentage == 100) {
      i++;
      
      $(html, body).scrollTo(0, checkpoints[i], {
        animation: {
          easing: linear,
          duration: timer[i]
        }
      });
    }
  }

  function scrollUp() {
    if(i > 0) 
      i--;
      
    $htmlAndBody.scrollTo(0, checkpoints[i], {
      animation: {
        easing: linear,
        duration: timer[i]
      }
    });
  }

I also added keyboard navigation, and put some arrows on the page for easier navigation. Also, after getting reviews from some non-technical people, I added the auto-play option so that all the lazy people would still be able to watch the whole presentation without moving a finger :P

This almost completed the whole page. Last additions were creating a preloader for the page which loaded the images of first 5 slides with a progress bar and then rest of the images are loaded in the background. If you want, you can take a look at the preloader.js to see how I did the preloading. Another thing was the share buttons and showing the count which was retrieved using PHP.

I hope this covered everything but if you get stuck anywhere, then feel free to add your comments! :)


Visual Website Optimizer’s editor component loads a website for editing using a proxy tunnel. It put a big restriction on what kind of websites could be loaded in it. Websites behind a firewall, the ones on a local network, or behind HTTP authentication could not be loaded using the tunnel. Other than those, even if the website did load in the editor, chances were that it could break on the frontend due to issues with JavaScript or AJAX communication.

You’d ask: why is there a proxy in the first place? Because, if a page contains an iframe on another domain, it cannot access its properties or functions. It is a security feature that browser vendors offer users to protect their privacy.

The Problem

So, our task at the frontend recently was to eliminate this troublesome middleman and find a solution for cross-domain iframe communication. We knew what the answer was: the PostMessage API. Provided a customer had VWO tracking code integrated on their website, we could load the iframe directly without a proxy and communicate with it using this API. The bigger question, however, was how to do it. The Editor had a lot of parent-child communication going under the hood for every task the user performed. When attempting to use PostMessage for this communication, we were faced with a couple of issues:


  1. Our legacy code had direct communication between the parent frame and the child frame at all places, i.e. the objects and functions in the child were accessed synchronously. PostMessage API, on the other hand, is completely asynchronous, and implementing such an API on the existing codebase would almost mean rethinking the entire logic and program flow all over again. We could foresee this asynchronous transition become a cause of a lot of race conditions within the Editor.

  2. Often, after sending a message to the other frame, we wanted to hear back a reply, for which we needed a decent two-way communication. A kind that would keep track of the sender and the receiver and could be identified across iframes using a unique identifier (to tie up the requests and responses).

  3. Since PostMessage uses string messages for communication (or structurally cloneable objects in the recent browsers), it put a big limit on what kind of data we could send during this communication. Directly accessing DOM nodes and sending around certain cyclic objects was no longer possible.

For instance, when you select an element in the child frame, it creates a new VWO.Element instance in the parent frame and asks it to open a context menu. The code looked something like this:

  $(elementSelectorPath).click(function() {
    var element = parent.VWO.Element.create(elementSelectorPath);
    parent.VWO.ContextMenu.showForElement(element);
  });

While, it might seem like a trivial problem to solve on the cover, deep underneath, we were faced with a race condition. The Element.create method asked the child frame to add a class to that element, and the ContextMenu.showForElement expected the class to have been applied by the time it was executed.

The Solution

We concluded that refactoring the code to adapt to the asynchrony would be one hell of a task and we had to find another way. We decided to write a wrapper around the PostMessage API to solve the above three problems. We called it please.js. We are currently giving it some finishing touches before we push it out to the community. Here’s how we did it:

  • We decided to build this library on top of jQuery Deferred API. While deferred objects and promises don’t exactly eliminate the asynchrony, they somehow bridge the gap between the two, making asynchronous code feel more linear and flattened. So, using that base, any piece of code that expected code prior to it to have been executed fully, could now be made possible without giving a lot of thought. In the above example, the transition to please.js looked like this:
  $(elementSelectorPath).click(function() {
    please(parent)
      .call('VWO.Element.create', elementSelectorPath)
      .then(function (element) {
          please(parent)
              .call('VWO.ContextMenu.showForElement', element);
    });
  });

Although this seems hackish at the first glance, it was a way to rapidly iterate over synchronous code and convert it to use promises and callbacks without giving much thought on the logic.

  • To establish a good two-way communication, we thought of thinking of each communication as a pair of messages: a request and a response. Under the hood, we identified each message using a timestamp it was initiated on, and created a request object with that identifier. We then send the request to the other frame, whilst storing it in the current frame in a hashmap. The other frame would then receive the request, perform an appropriate action and send back a response. After a response is received, the request would be deleted from the hashmap. To make things easier for us, we created a set of functions to make certain frequent tasks easier. For instance, getting / setting a property and calling a function were the most common tasks we performed. The code for these tasks now looked like this:
  please(parent).get('window.location').then(function(location) {
    // use location here
  });

  please(parent).set('foo', 'bar').then(function () {
    // do something here
  });

  // reload the child window.
  var childWindow = $('iframe#child').get(0).contentWindow;
  please(childWindow).call('window.location.reload');

A paradigm shift, yet the logic remained unaffected. Exactly what we wanted.

  • The last task was a big one. We had a lot of code in the parent frame directly accessing the child frame’s DOM. While this is not advocated as a good practice, such problems are often faced when building upon and improving legacy code. With PostMessage, you can no longer access the child’s DOM in any way. But we came up with a smart solution. We know that jQuery is a wrapper around the traditional DOM. We created a PostMessage wrapper around jQuery itself! Which makes impossible turn possible:
  // set #bar's height in child = foo's height in child
  var pls = please($('iframe#child').get(0).contentWindow);
  pls.$('div#foo').height().then(function (fooHeight) {
    pls.$('div#bar').height(fooHeight);
  });

  // DOM elements are returned back as please.UnserializableObject
  // which can then be passed back to please.$ to do more stuff
  pls.$('<div>hello world</div>').then(function (newDiv) {
    pls.$(newDiv).appendTo('body');
  });

This was something that I thought of during one of the hackathons we host at Wingify. Turned out to be very fruitful!

Conclusion

In my personal opinion, I believe using promises for such a large transition has greatly impacted the way I think about frontend web development. It is a way forward for rapid asynchronous development, and yet having a flattened synchronous-like code structure.

please.js will be opensourced soon, so keep an eye out on the blog for updates!


I clearly remember the summer of 2010 when we were about to launch our product Visual Website Optimizer out of beta and almost all the conversations I and Paras had were either around acquiring our initial customers or about the ever increasing load on our single Linode 512MB VPS. Three years down, we still end up discussing about the same things but at a completely different magnitude. The customer base has increased to 2600+ accounts across 75+ countries and our geo distributed architecture on a set of 30+ servers currently serve close to 8,000 requests per second.

In this amazing journey what we also managed to do is try our hands on a bunch of different technologies, tools and libraries. Many a times, the available stuff didn’t fit our scaling needs and we had to craft our own versions. With the medium of this blog (which was long due), my team will try to talk about all the learning we keep having in our day-to-day engineering work at Wingify.

In the next few posts, we will walk though the evolution in our architecture (from standard LAMP to a delicately configured openresty CDN & DA (Content distribution & data acquisition network) environment; from a single data center stack to a distributed system across the globe), code rewrites / refactors we had to do, various benchmarks we relied on and of course all the learning we had from this exercise. We will showcase and write about the small / big tools and libraries we wrote to scratch our own itch.

If you ever have any question, suggestion, feedback or you want to discuss anything or just want to drop a hello; I would love to hear from you. Please feel free to get in touch with me sparsh@wingify.com or with our engineering team engineering@wingify.com directly or via comments section in this blog.

Hope to see you again soon when we post our first engineering article on this blog. Please subscribe the blog to stay updated. We’re out to change the way software is written, so come along and share our journey!

– Engineering @ Wingify