2

I'm making a single-page web app using BackboneJS (jQuery + Underscore), and I'm testing this in various browsers and on various devices. Eventually it'll become a Phonegap app.

It works great, but on an iPhone 4s (iOS 8) all of my routes render quite slowly (around a second, roughly). I've yet to resolve the 300ms click delay, but I'll worry about that later. The problem is made a lot worse by one of my routes, that has a particularly large number of views (~30), and can take up to 5 seconds to render on this device.

Here's an example of my route:

'some_route': function(){
  if(APP.controller) APP.controller.destruct();
  APP.controller = new SomeController();
},

All of the views are instantiated and rendered during the controller instantiation (and should update the DOM individually). Here's an example of the view render function that gets called for a number of views when this route loads:

render: function() {
  var modelData = this.model.toJSON();
  this.$el.html(this.template(modelData));
},

Interestingly, if I add an alert under the controller instantiation in the route, it happens before the UI changes appear (3-4 seconds before). And, all UI changes appear at once. Given that JavaScript is single-threaded, this doesn't make much sense. Unless jQuery is somehow handling .html() calls asynchronously, or if the browser is applying a delay to visual DOM updates...

I appended a timestamp to the end of the template (so each view renders with a timestamp at the end) to see how long it was taking each to execute. The difference between the first and last was only 0.03s, yet it still took nearly 5 seconds for the browser to actually render the changes onto the screen (during this time, it was unresponsive).

I poked around with the render function and found that doing the following massively increases the performance, but it doesn't solve my problem:

render: function() {
  var modelData = this.model.toJSON();
  this.template(modelData); //call anyway to gauge performance impact
  this.$el.html('');        //call anyway, but with nothing
},

Could this be a memory problem? Is it to do with backbone navigation (URL changes)? Has anyone else come across this? I've searched around, but I really haven't had much luck finding anyone with the same issue.

UPDATE

Simply adding display:none for the view elements actually also speeds this up considerably. Clearly doesn't solve the problem, but it's beginning to look more like a memory issue than anything else. I'll experiment with speeding this up through CSS and image optimisations and will post a solution if that works.

CoderCreative
  • 225
  • 1
  • 2
  • 9

2 Answers2

1

I've found two solutions.

First, the page is burdened with heavy background images. Commenting out the CSS for these, and stripping down the CSS for the views that are being loaded in at the troublesome route, cuts the delay in half. Reducing the resources being used by the application (even background images) seems to directly reduce the delay.

So, one solution is to optimise the images and CSS. Less memory used, the better.

Secondly, I've discovered that it's possible to show a loading screen during the route transition, significantly reducing the UX impact. I'm using something like this:

'some_route': function(){
  $('#overlay').show();
  setTimeout(function(){
    if(APP.controller) APP.controller.destruct();
    APP.controller = new SomeController();
    $('#overlay').hide();
  },30);
},

This way, the browser has time to display the loading overlay before the onslaught of view renderings -- which is what seems to cause the delay -- at the end it removes the overlay.

Of course, if there is an event that gets emitted after the browser finishes VISUALLY rendering the changes (not just after the javascript execution ends), this could be done in a much better way.

CoderCreative
  • 225
  • 1
  • 2
  • 9
1

How many views do you have? if all the views are rendered when you instantiate the controller that could be a slow function, try adding something like this to check the time it takes

    var timeStart = performance.now();
    APP.controller = new SomeController();
    var timeEnd = performance.now();
    console.log('time ' + (timeEnd - timeStart) + ' ms');

it's true that JavaScript is single-threaded but you have to remember how the browser executes all the tasks, please take a look to the second answer for this question Why is setTimeout(fn, 0) sometimes useful?.

Community
  • 1
  • 1
jorar91
  • 130
  • 7
  • Thanks for your answer, the link you provided helped a great deal in understanding what was happening here -- I'm marking this as the accepted answer for that reason. Until now I was not aware of these different queues in the browser. To answer your question; there were between 30 and 40 views being constructed when the controller was instantiated, but the javascript was executing fast enough. The problem was that the browser on the iPhone updated the all of the views at once AFTER the execution of the of ALL of the Javascript, but thanks to your link I now understand why. – CoderCreative Aug 10 '15 at 18:48