Skip to content
This repository

slow response when clicking item in large listview on mobile devices #4340

Closed
mruffolo opened this Issue May 11, 2012 · 45 comments
Michael Ruffolo

On mobile devices, tested on iOS5 and Android ICS/Chrome beta, using the doc's list performance test page http://jquerymobile.com/demos/1.1.0/docs/lists/lists-performance.html, if you click/tap any of the items, there is a 3-4 second delay before the button press is displayed and then another 1-2 seconds before the page transition occurs.
This is exacerbated by the size of the list, larger lists have worse performance.
It seems this wasn't much of an issue in 1.0.1

Björn Endre

I can verify this issue. Tested on Ipad3 and HTC OneX I see this behaviour on my lists:

Small lists: 1-2 s delay
Medium lists (up to at least 300-400 items): 5-6 s delay.
Big lists (in my case 900+ items): 34(!) s delay.

Maurice Gottlieb

Here are some fiddles (to test lists outside the docs)
50 entries: http://jsfiddle.net/MauriceG/snJ2T/show/light/
100 entries: http://jsfiddle.net/MauriceG/CqgJP/show/light/
over 500 entries: http://jsfiddle.net/MauriceG/dchJA/show/light/
Comparison jQuery Mobile 1.0.1 with over 500 entries: http://jsfiddle.net/MauriceG/7rKqM/show/light/
over 2500 entries (just click on wlan!): http://jsfiddle.net/MauriceG/wZCcE/show/light/

1.1.0: searching in 500 entries list on iPhone 4GS / iPad3 still passable; on desktop still performant,
2500 entries may not for every day use and here just for testing ...

Maurice Gottlieb

edit: I just tested the search performance...
If I scroll down may to entry 350 in the 500 entry list and click a list item,
safari on iPad 3 crashes

Sven Franck

some input from JQM forum, maybe useful: bad performance for large lists best solution

Kin Blas
jblas commented May 14, 2012

@toddparker @gseguin @frequent

I did a quick profile over the weekend of lists-performance.html on my iPad. You can browse the results here:

http://goo.gl/gRFga

The biggest offender with 5 seconds seems to be related to a blur() call results in a pattern of calls involving fn.init and fn.removeClass ... I didn't count the number of times the pattern repeated but I would hazzard to guess that it's doing it for each item in the listview.

Here's a snippet:

$.fn.trigger - 5000
$.fn.each - 5000
$.each - 5000
$.isFunction - 0
$.isWindow - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 1
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$.fn.init - 0
$.fn.jqmHijackable - 0
$.type - 0
$.fn.init - 0
$.fn.closest - 1
$.fn.not - 0
$.fn.addClass - 1
$.fn.init - 4
$.fn.not - 2
$.fn.blur - 4991
$.fn.trigger - 4991
$.fn.each - 4991
$.each - 4991
$.isFunction - 0
$.Event - 0
$.isWindow - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$.fn.init - 0
$.fn.removeClass - 1
$._data - 0
$.acceptData - 0
$.isWindow - 0
$.Event - 0
$.isWindow - 0
$._data - 1
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$._data - 0
$.fn.init - 0

Kin Blas
jblas commented May 14, 2012

Hmmm, well git munged my indents, so you'll have to browse the data at the link I posted above.

  • Kin
Dave

@jblas: Nice work, It looks like you've come across something that needs to be optimized. What code profiler are you using?

Kin Blas
jblas commented May 14, 2012

@dcarrith

I'm using the profiler I wrote so we can profile directly on devices here:

https://github.com/jblas/profiler/

I need to update the README.md so that it shows folks how to generate the JSON and use the viewer.

Dave

@jblas: thanks for info and link.

Dave

Could the culprit be line 1263 in jquery.mobile.navigation.js (line 3548 of jquery.mobile-1.1.0.js)?

In the vclick handler:

$( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur();

If I'm reading that correctly, it blurs all elements assigned the ui-btn class in the currently active page that aren't the link that was clicked. On a list of 500+ list items, that would be quite a few that are not the one that was clicked.

Is that line needed for something in particular? Why not just focus the element that we know was clicked (even though, it should be focused already) rather than blur all the ones we know were not clicked.

I commented it out for now in my local copy to see what happens.

Kin Blas
jblas commented May 14, 2012

We'd have to ask @scottjehl why it's done that way. That particular chaining sequence lines up with the profile call graph that I pasted, so that's probably it.

Todd Parker

Thanks @jblas - I just ping'd @scottjehl to look at this.

Dave

After running with that line commented out for that past week and observing to see if there is any change in how links react to a touch or click, I did notice some strangeness that doesn't make much sense. With touch events, it seemed to be less accurate in finding the closestLink (even though that line was above the blur line). I may have just been looking too hard for a problem and maybe I was just fat-fingering the li items or something.

So, this morning I added this line as a replacement for the blur line (in case there is any reason why it was trying to blur every link except for the clicked link):

$( link ).focus();

I'll do some more testing to see if there is any difference in how it reacts to touches and clicks (compared to nothing). If I still experience the fat-fingering-like reactions to touching li items, then I guess it's just my fat fingers. ;)

By the way, I did notice a pretty dramatic performance improvement in touch response (no surprise there I guess). It's also worth mentioning that I've been running the jQuery Mobile Widget I built to lazy-load list items so that also helps with how quickly the linked page is loading. I have my app to a state where the performance all around is quite good actually. If anyone wants to know more about my lazyloader widget, here's the forum post where I announced it to gauge interest: http://forum.jquery.com/topic/jquery-mobile-widget-lazyloader-coming-soon

Scott Jehl

Hey everyone.

So, I did a little digging through commits to provide a little context into this line. It was added during the early releases to clean up a trail of focus/hover states that we were seeing while navigating jQM apps. Basically, focusing/tapping on another button on the page wasn't effectively blurring a button previously in focus, so button states were getting out of line.

While working on the problem, I'm fairly sure we tried manually triggering focus() on the clicked link (as @dcarrith kindly suggested above), but it still wasn't effectively blurring (or perhaps more specifically, triggering mouseout on) the other buttons on the page. This would be worth re-testing. I think the problem was most commonly seen in iOS, but it may have been on Android browsers as well.

Here's the commit
823485c

In hindsight, the change was overly-agressive, and the performance findings above verify that. Really, we just needed to find only the buttons in the active page that have stale states and remove their classes.

With our theme framework, this is a little trickier than it sounds. States are tied to letter-based themes, so the hover and focus state classes are variable. The blur and vmouseup handlers bound to these buttons keep track of that button's theme classes, so triggering blur on them was at least a clean way to clean things up. In the end, using the event triggers still might be the best approach; that said, I think we can remove the performance problem by minimizing the number of buttons that we're blurring by using a smarter selector (looking for class attributes that contain values with those prefixes).

Looking at the buttonMarkup plugin, the blur event removes the following classes from a button: ui-btn-hover-[theme], ui-btn-down-[theme], and ui-focus.

So those are the classes we'll need for our lookup. Something like the following might work...

$( "[class*='ui-btn-hover-'],[class*='ui-btn-down-'],.ui-focus", "." + $.mobile.activePageClass ).not( link ).blur();

In a brief console test, that seems to winnow it down to just what we need.

Anyone have time to give that some analysis?

Kin Blas
jblas commented May 31, 2012

I think the problem is still going to persist because we're still searching throughout the entire document, maybe even several times since there are multiple selectors specified. Can't we just track whatever is currently focused/active by bottlenecking things through function(s) that manage this? This would eliminate the need to search through the entire document.

Scott Jehl
Gabriel "_|Nix|_" Schulhof
Collaborator

We're already tracking the active link with $activeClickedLink in jquery.mobile.navigation.js and turning it off upon page change. In fact, when I removed the troublesome line, I saw no stale focuses on my desktop browser (FWIW).

Gabriel "_|Nix|_" Schulhof
Collaborator

Can we assume that the selector will only match buttons? If so, perhaps we can call the blur handler directly, without going through jQuery's event firing mechanism.

Kin Blas
jblas commented June 01, 2012

I guess the question for @scottjehl is if there are .ui-btn elements that are not links that can be focused/active. Also, firing the event handlers directly won't work if any of the btns use delegated handlers.

I did a quick profile without scott's proposed change on an iPad 3 running iOS 5.1:

http://goo.gl/kPIQ7

and with scott's change:

http://goo.gl/hSpJa

The old selector took 5ms to execute, the new one takes 10ms, but the overall blur call went from 5070ms down to 167ms which is a big improvement ... but, 177ms is still pretty slow in my opinion, especially on an iPad3, considering that this time doesn't include the actual loading/change of page.

Scott Jehl

Hmm. Can someone test if @gabrielschulhof is right? If so, sounds like we can just remove this line without a regression. To test, just remove the line and navigate around on iOS and a desktop browser too, see if hover states stick as you go back and forth between pages that have been visited.

Dave

After commenting out the line:

$( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur();

I tested on an original iPad running iOS v5.0 and am not seeing any stuck hover states while going back and forth between normal pages, pages with data-dom-cache=true, or data-role=dialog pages.

FWIW: I've also been testing in desktop Chrome on OSX, Firefox on OSX, Stock Android Browser on v2.3.7 and phonegapp webview, Stock Android Browser and phonegap webview on ICS running on Touchpad w/CM9, Blackberry tablet 2.0 stock browser and phonegap on a playbook, and last but not least, IE on Windows 7.5 on a HTC Titan.

Gabriel "_|Nix|_" Schulhof
Collaborator

Sounds like @dcarrith pretty much nailed it ...

Todd Parker

@gabrielschulhof - you were going to land @dcarrith's suggestion, right? I'd like to land the change and live with it for a while to see if any issues shake out.

j anmella

I am developing a handheld application... using Android+Eclipse+jquerymobile+phonegap
And of course, I "crashed" with the same problems as yours...

One of them, poor and slow performance in event time response (in a list of elements)

I've tried all options depicted here.....
But, playing with event handlers i've found the solution.... at least it works perfect for me....
(and without the need to use fastclip.js script, nor changing the jquery.mobile-1.1.0.min.js source code...)

I'm relatively new to jquerymobile, but this works perfectly for me , so I hope it helps you too.....

How to arrange the slow performance in event clicks on a large element list

If you create a dynamic list (hundreds of Iist elements <li> into <ui> tag)
do not use the "href=" tag to bind the callback event procedure as usual....

Instead, try to do the following:

Let's suppose we want to display
a list of elements....
each element of the list has a <name> field and a <id> field

And we want to bind an onclick callback procedure to display another screen
based on the <id> field for each element clicked ..

In the page.html we have declared the container into we'll fullfil the list items:

<ul id='container'>
</ul>

and in the javascript code
we have something like...

var htmlStr = "<ul id='list_container' data-filter='true' data-role='listview' data-inset='true'>";
for (i=0; i<list.length;i++ ) htmlStr += "<li><a href=\"javascript:callback_click('"+list.id+"')\">"+list.name+"</a></li>";

//Then we update the dom
$("#container").html(htmlStr);

//And we make specific calls to jquerymobile to re-build the look-and-feel
$("#container").listview();
$("#container").listview('refresh');
$("#container").trigger("create"); //this one is always needed , or it won't work...

//
function callback_click(id)
{
alert('Jump to item list '+id);
}

If we try this, the response time for each click is of 300 ms, as expected....
Thinking of it, what if we try to capture the events in the first place?

I changed the javascript code like this:

//declare a variable to trap the scroll event
var there_is_scroll=0;

//bind a procedure to trap the scroll events
//we could use too scrollstop (when scroll has finished)
function onload() {
$('#page_div').bind("scrollstart ", function (ev) {
there_is_scroll=1;
});
}

....
.....
.....

//The code to fill the list

var htmlStr = "<ul id='list_container' data-filter='true' data-role='listview' data-inset='true'>";
//supress the href
//for (i=0; i <list.length;i++ ) htmlStr += "<li><a href=\"javascript:callback_click('"+list.id+"')\">"+list.name+"</a></li>";
//instead, declare an id for each item
for (i=0; i<list.length;i++ ) htmlStr += "<li><a id='id-"+list.id+"'>"+list.name+"</a></li>";

//Update the dom
$("#container").html(htmlStr);

//Make specific calls to jquerymobile to re-build the look-and-feel
$("#container").listview();
$("#container").listview('refresh');
$("#container").trigger("create"); //this one is always needed , or it won't work...

//bind the touchend event to each item
for (i=0; i<list.length;i++ )
{
var item_id="#id-"+list.id;
$(item_id).bind("touchend", function (ev) {
//if there is a scroll event, we ignore the event, do nothing
if (there_is_scroll==1)
{
//alert(' we had scroll');

there_is_scroll=0;
//ev.preventDefault(); // there's no performance difference if uncommented
return;
}
id = $(this).attr('id');

alert('Jump to item list '+id);
// Do pange change here based on id
});
}

....
....

This works perfectly and quick for me

I suppose this is aprox the order in wich the events are triggered...

touchstart
touchend
scrollstart
scrollstop
tap /taphold
click

I tried to gess the order of the events triggered with....
function onload() {

$('#page_div').bind("touchend", function (ev) {
    console.log('touchend');
});

$('#page_div').bind("tap ", function (ev) {
console.log('tap');
});

$('#page_div').bind("scrollstart ", function (ev) {
console.log('scrollstart');
});

$('#page_div').bind("scrollstop ", function (ev) {
console.log('scrollstop');
});

$('#page_div').bind("taphold ", function (ev) {
console.log('taphold');
});

}

How to speed up touch input button

I have another issue with another feature, I need to
simulate an input keyboard and refresh a text as the user taps each button....
(override the default input method)

I have in the html :
<form name="form">
.......
<input text disabled id="display"> to display the input responses

And table with several buttons:
<td height="60px" width="60px" id="button1">1</td>
....
....

To achieve this I had to:
Bind a touchstart event for each button to capture the event as quickly es possible:

$('#button1').bind ("touchstart", function (ev) {
// Do refresh
content=document.form.display.value;
document.form.display.value=content+"1";
});
//(with touchend we would capture each finger release)
....
.....

and to accelerate the refresh:

//avoid navigation-bar refresh and other useless stuff......
$('#index-page').bind("touchend", function (ev) {
ev.preventDefault();
ev.stopPropagation();

});

If someone of you want more details , i gladly will explain them for you

CONCLUSION

As a general conclusion, don't use the <href=...> , nor <onclick=... > event bindings any more!
Instead,you must attach the binding procedures in jquerymobile (with .bind or .live)
Then, you must customize the event propagation to fit your needs and at the same time preventing
jquerymobile doing superfluous work wich leads to slow performance.....

I thank all you for the ideas and comments, and hope to contribute actively to the project community

Dave

Today, I got all set up to submit a "suggested" patch to remove the reliance on window.history.back() which is called in the block of code "if( $link.is( ":jqmData(rel='back')" ) ) { ... }" in jquery.mobile.navigation.js. The call to window.history.back was contributing to the jumpiness on Android (and, I think other platforms too).

Since I got all set up to run the unit tests, I went ahead and commented out the aforementioned "$( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur();" line and ran the unit tests. They all passed. I'd be glad to submit a pull request for that change first before submitting the main pull request for the partial rewrite of the click handler (which should only affect clicks on buttons with data-rel="back" attributes). I'll be submitting the main pull request tomorrow sometime as long as my testing goes well.

j anmella

dcarrith, I'm very glad to hear those good news, please submit the patch to arrange all these features..
Thanks to all other posters too, to share their knowledge,,,,

Now , (perhaps with the patch this is arranged too), but I would ask you a litlle help, guys....

I've got some strange behaviour in the callbacks when
trying to simulate input keyboard and refresh a text as the user taps each button....
(each key is a input button)

When putting back the color of keys onpressed, there are sometimes the color change does not take effect
Other strange behaviour: when pressing quickly the keys, sometimes the whole screen "refreshes"

The html:

KEYPAD
1 2 3 4 5
6 7 8 9
BR , - C B

the callbacks....

//on touchstart
$('.boton').bind("touchstart", function(ev){

ev.preventDefault();
ev.stopPropagation();

    var key_id = $(this).attr('id');
    $(this).css("background-color","#BBBBB9");      

    var content=document.form.content.value;

    if (key_id=='back')
        {
        content=content.substr(0,content.length-1);
        document.form.content.value=content;
        }
    else if (key_id=='CLEAR')
    {
    document.form.content.value="";
    }

    else
        {
        document.form.contenr.value=content+key_id;
        }   

});

//on touchend
$('.key').bind("touchend", function(ev){

    ev.preventDefault();
    ev.stopPropagation();   

    var key_id = $(this).attr('id');

    $(this).css("background-color","#FFFFFF");

}

I had thought to use a plugin for phonegap, for android input method, but there is a plugin to show the keyboard only... and is not customized...
I'd like to have a fixed portion of the screen with my stuff (html) , my data and some text input types and a "android" keyboard in another portion of the screen fixed too, which I could customize, with arrows keys (input buttons with the picture of arrows) , and the rest of keys,
allowing the scrolling of lines, etc..... I don't know if i can use the plugins to share both worlds, native and html....

If I just could make my html + javascript code work quick and nice together with jquery mobile....

Could someone help me, please?

Sven Franck

@dcarrith:

I'm trying JQM listviews with $( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur(); commented out. It may be my crummy iOS3 or are buttons in listviews now far less responsive? You really need to "press hard" to trigger a click now. Anyone else notice this?

Ghislain Seguin
Collaborator

@frequent I don't have an iOS3 device. Could you make a video of the behavior you're describing?

Sven Franck

@gseguin: on the road. Can you wait until tomorrow night?

Ghislain Seguin
Collaborator

absolutely!

Todd Parker

Alight @scottjehl figured out the root of the issue here. In 1.0, we first scrolled to the top of the page before starting a transition. In 1.1, we wanted to avoid this jump so we now fade the page out first by default. As it turns out, devices like an iPhone seem to keep the page above your scroll position in memory when doing a transition, but not the part of the page below.

So if you're on a long, complex page like the listview performance page with 500 items, clicking the first list item will work pretty well, but one halfway down will be very slow and a bottom one will crash the browser. Why? Because the browser is trying to animate a really massively tall page when scrolled down due to how it handle things. Test here: http://jquerymobile.com/test/docs/lists/lists-performance.html

As a test, we added another condition to say that if you're either coming or going to a page where the scroll position is 3x the height of the device's screen, skip the transition. This makes the navigation back to it's snappy self. You'll notice we're using scroll position, not screen height here because I can navigate to a 10,000 pixel tall page fine but if I need to navigate away from that same page when scrolled down (or nav back to this with a scroll position restored), it will need way more memory to run the transition and could be slow or fail.

This 3x value is just a starting point, we can use whatever criteria we want. Here is the change to line 28 in jquery.mobile.transition.js to give this a spin:

none = !$.support.cssTransitions || maxTransitionOverride || !name || name === "none" || Math.max( $( window ).scrollTop(), toScroll ) > screenHeight * 3,

The question is how to layer this in. Do we just pick a value for this for 1.1.1 as a safely valve for performance now, then expose a config option for 1.2 to set the criteria? This is parallel to maxTransitionOverride - this sets the screen width that shuts off transitions automatically, this new one is like ```maxTransitionScrollOverride''' where we nix transitions based on vertical scroll position. Naming could be cleaned up to make this clearer.

@gseguin or @johnbender - do you want to give this a look and think about how we should get this in?

Todd Parker

After discussing with @gseguin, we're going to land this new transitions scroll fix as an option called maxScrollForTransition - pipe up if you have better ideas for a name :)

Ghislain Seguin gseguin closed this issue from a commit June 22, 2012
Ghislain Seguin Fixes #4340
Conflicts:

	js/jquery.mobile.transition.js
8e570e3
Ghislain Seguin gseguin closed this in 8e570e3 June 22, 2012
Ghislain Seguin
Collaborator

I ended up using a function: $.mobile.getMaxScrollForTransition since the screen size is not necessarily constant.

Jason Lee jlsync referenced this issue from a commit in jlsync/jquery-mobile June 22, 2012
Ghislain Seguin Fixes #4340 4ca4152
Todd Parker

Thanks so much @gseguin - this fix seems to get performance back to to where it should be. So good to have this one closed out with a solid fix. Is this also in 1.1-stable?

Let us know if you're still seeing issues.

Sven Franck

@gseguin: hardware issues... video coming eventually....

Ghislain Seguin
Collaborator

@toddparker, indeed I cherry-picked the fix into 1.1-stable

Todd Parker

@frequent - are you seeing an issue with this fix? It just automatically sets the page transition to none if the page you're on or going to is scrolled down very far.

Maurice Gottlieb

I've tested the list performance page with iPad 1 iOS 4 and it works as fast as with 1.0.1.
Also tested the above fiddle with 2.780 list entries with iPad 3 and it's very fast.

Good job @gseguin, indeed!

Ghislain Seguin
Collaborator

All credit goes to @scottjehl, I just integrated. the patch.

Sven Franck

@toddparker @gseguin: it's fast but sort of hiccupy on iPad 1, iOS3.2 (I guess thats almost an oldtimer...). Here is the best video I managed with my phone... another oldtimer :-)

Todd Parker

@frequent - from what I can see, that's just the slower CSS rendering of all the gradients and rounded corners so it's not a regression. @MauriceG - wow, that 2,600 list item example is intense. I can confirm navigation works quickly when tapping the last item - maybe 3 seconds to show the result page and immediate active state on my 4S.

In general, if folks are going to use really long lists they should tune their CSS to reduce the amount of gradients, shadows and roused corners to speed up the lists by overriding the styles. Flat = fast.

Good to hear this seems fixed completely.

Dave

@gseguin @scottjehl @toddparker - Is there a reason why getMaxScrollForTransition is a function rather than an global config variable like so many of the other global configs documented here: http://jquerymobile.com/demos/1.1.0/docs/api/globalconfig.html

I was messing around in the code today and started thinking that getMaxScrollForTransition should be more like the rest of the globalconfig variables. So, the relevant part of the transitions code would look something like this instead:

//----------------------------------------------------------------------------------------------

// generate the handlers from the above
var sequentialHandler = createHandler(),
simultaneousHandler = createHandler( false ),
getDefaultMaxScrollForTransition = function() {
return $.mobile.getScreenHeight() * 3;
};

// Make our transition handler the public default.
$.mobile.defaultTransitionHandler = sequentialHandler;

//transition handler dictionary for 3rd party transitions
$.mobile.transitionHandlers = {
"default": $.mobile.defaultTransitionHandler,
"sequential": sequentialHandler,
"simultaneous": simultaneousHandler
};

$.mobile.transitionFallbacks = {};

// Set the maxScrollForTransition to default if no implementation was set by user
$.mobile.maxScrollForTransition = $.mobile.maxScrollForTransition || getDefaultMaxScrollForTransition();

//----------------------------------------------------------------------------------------------

That way, developers can override it in the "standard way" in mobileinit (i.e. following the standard outlined in the globalconfig documentation above):

For example, something like this:

// Set this to a really high number so it won't prevent the smooth scroll
$.mobile.maxScrollForTransition = ($.mobile.getScreenHeight() * 1000);

Compared to what would be required now (unless I'm missing something):

// Set this to a really high number so it won't prevent the smooth scroll
$.mobile.getMaxScrollForTransition = function() {
        return $.mobile.getScreenHeight() * 1000;
};

I guess it doesn't really matter, but I was just curious why the getMaxScrollForTransition is being treated differently than the rest of the global configs.

Ghislain Seguin
Collaborator

Hi @dcarrith,

The reason for using a function is that the screen size can change during runtime on desktop browser or after orientation change on mobile devices. A function gives the user more flexibility: it can return a constant or a dynamic result based on the size of the screen.

Dave

Ah, okay - that's brilliant. Thanks!

Elliot Smith townxelliot referenced this issue from a commit July 30, 2012
Commit has since been removed from the repository and is no longer available.
Brad Smith

I've read the entire thread and don't fully understand the fix. I have this exact problem (anemic list performance). How can I go about correcting it? Do I simply set getMaxScrollForTransition to something or would one of the other solutions work better?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.