5

Problem Solved!

This problem (specific to my configuration) has been solved by Dhoelzgen and Matthew Blancarte as per the accepted answer. The jist of the problem was that I was binding 'click' events to all the .inventory_item elements when I should have been using jQuery's on method to delegate the event handling, like so:

<head>
    <script>
        $(document).ready(function(){

            $('#inventory_index').on('click', '.inventory_item', function(){
                alert('Click event fired!');
            });

        });
    </script>
</head>

Using this technique I've greatly, greatly increased the responsiveness of my app.

Continue reading for all the details ...

Overview

I'm working on an inventory app that runs in a 'single page' (e.g. www.myapp.com/app.php) and uses jQuery to execute XHR's to load the various content in and out of DIV's.

I'm using jQuery 1.9.1 and jQuery UI 1.8 (because I have to for legacy reasons).

The Problem: Slow Click Events

The problem I'm having is that the click events get slower and slower as the DOM tree grows larger. The delay is currently about 2 seconds when ~1000 items are returned from a search.

Here's the example jQuery:

<head>
    <script>
        $(document).ready(function(){
            var inventory_item = $('#inventory_index.inventory_item');

            inventory_item.on('click', function(){
                alert('Click event fired!');
            });
        });
    </script>
</head>

And the HTML:

<div id="inventory_index">
    <div class="inventory_item">Inventory Item 0 <img src="inventory_item_0.jpg"></div>
    <!-- 999 Iterations -->
    <div class="inventory_item">Inventory Item 1000 <img src="inventory_item_1000.jpg"></div>
</div>

At first I assumed it was because of the images that reside within each of the .inventory_item's, but after implementing lazy-loading I discovered that the issue had more to do with the number of elements in the DOM than it did with the images themselves.

Attempted Solution

As you can see in the example code above, I've already tried to implement the best solutions I could find over the past couple of days. Namely, wrapping the collection of .inventory_item's in an ID-able #inventory_index element to give jQuery a hint as to where it should be looking.

And, additionally, creating a javascript object to try and shave even more time off the DOM search (although, to be honest, I'm not sure exactly how that works, or if it's helping at all).

Has anyone else run into this problem and have any solutions or advice they could share?

Current Best Idea

As of right now, the only way I've imagined this could be solved is to simply reduce the number of elements in the DOM tree by loading less results into the #inventory_index. This is an option, but I'd really like to retain the ability to load up hundreds, if not thousands of .inventory_item's into the index.

BONUS

Oddly enough, the mouseenter and mouseleave events fire instantaneously. You can see a similar problem here:

jQuery delegate performance on the click event on large lists - slows down if you dynamically add more elements?

Community
  • 1
  • 1
AJB
  • 7,389
  • 14
  • 57
  • 88
  • You should try binding the event handler as you add it to the `#inventory_index`. event delegation is slow and you should definitely see better performance with direct binding. – Selvakumar Arumugam Apr 26 '13 at 20:06
  • Hi @Vega, so you mean I should 'execute' the bind within the while loop of my PHP/MySQL result? Also, I've read the opposite about delegated bindings; are they not supposed to be faster? – AJB Apr 26 '13 at 20:08
  • 1
    Just a minor point, but unless I *really* overslept this morning, you're probably using jQuery 1.9.1. Yes, I know: *pedantry*! But, this is the internet, after all. – David Thomas Apr 26 '13 at 20:10
  • @AJB Are you using AJAX to add up the `inventory_item`? If so bind the handler inside `success` callback when appending the result to the `#inventory_index`. – Selvakumar Arumugam Apr 26 '13 at 20:10
  • Ahhhh ... okay, I've give that a shot. Thanks @Vega! – AJB Apr 26 '13 at 20:11
  • @AJB is `#inventory_index.inventory_item` supposed to be `#inventory_index .inventory_item`? otherwise it will select 0 elements. – Kevin B Apr 26 '13 at 20:13
  • @AJB The reason it takes 2 seconds on large DOM trees is because you are using direct event binding... – Matthew Blancarte Apr 26 '13 at 20:15
  • Hi folks, thanks for this info. I'm looking into it and I'll update this question once I've done my testing. – AJB Apr 26 '13 at 20:22
  • @AJB My bad.. in your case specifically event delegation would be better. Matthew is right. – Selvakumar Arumugam Apr 26 '13 at 20:23
  • 1
    I was doing a bit of homework before implementing a similar system. I believe you just saved me whole cat herds of headache. Great question and great answers. Thanks! –  Aug 05 '14 at 08:30

1 Answers1

13

What about using jQuery's on method to attach an event handler like this:

$('#inventory_index').on('click', '.inventory_item', ...)

This way, you would only add a single event handler, instead of one for each inventory item. Haven't tested it, just stumbled about the fact that you add a lot of event listeners.

Some explanation:

If you use $('#inventory_index .inventory_item') as a selector, you end up binding a single event handler to each inventory item which is a problem especially if you have many of them. On the other hand the #inventory_index selector above just adds a single event handler to the element used as a wrapper, which is responsible for handling all the clicks on the elements filtered by the second selector, which is the second argument .inventory_item in the on method call.

dhoelzgen
  • 1,150
  • 7
  • 15
  • Hi @dhoelzgen, thanks for the answer, but that's essentially what I've already done ... Unless you can see that there's a big difference between creating a JS object from $('#inventory_index.inventory_item')? – AJB Apr 26 '13 at 20:10
  • Jep, you have a selector for all your inventory item elements. The other selector is only for the element used as a wrapper. – dhoelzgen Apr 26 '13 at 20:12
  • 2
    @AJB Yes, there is a huge difference in this answer, although it isn't thoroughly explained. Your need to delegate the events (as shown in this answer). You are currently using direct binding, which is why it's slowing down. See this answer: http://stackoverflow.com/a/8111171/614152 – Matthew Blancarte Apr 26 '13 at 20:12
  • Also, look for the section explaining Direct and Delegated binding: http://api.jquery.com/on/ – Matthew Blancarte Apr 26 '13 at 20:13
  • Thanks Matthew for further explanation of my answer, it's a bit late for me I think ;) – dhoelzgen Apr 26 '13 at 20:14
  • Thanks @MatthewBlancarte for the further explaination. I'm doing some testing now and I'll update this question once I've got more results. – AJB Apr 26 '13 at 20:22
  • 1
    I added the explanation to the answer to make it a bit more clear – dhoelzgen Apr 26 '13 at 20:24
  • This worked beautifully! Thanks @Matthew Blancarte. I'm not going to implement this technique on all my other event bindings and tune, tune, tune. – AJB Apr 26 '13 at 20:46
  • I happened to have same problem. I'm using infinite scroll method so my parent div lists 1000s of products when user scrolls down. Luckily I tried this delegated method at my own and it reduced event firing time significantly. But still the lag is there. I think loading 1000s of elements at single page isn't what browser can handle efficiently or maybe is limited by memory. I don't know of other js library are better at handling this issue. – wp student Dec 12 '17 at 07:24