Search code examples
jqueryfirefoxscrollcustom-data-attributesizzle

Using jQuery to select elements with data attributes assigns a null ID to its parent elements


This is truly bizarre. If I use jQuery's .find() to find child elements that have data attributes during a scroll event, then scrolling the page will repeatedly add and remove an ID to the parents of those elements.

It's difficult to describe, but here's a reproducible test case: http://jsfiddle.net/8fouvx9p/

var groups = $(".group");

$(window).bind("scroll resize orientationchange", function() {
  showGroup();   
});
               
function showGroup() {
  $(groups).each(function() {
    var group = $(this),
        elements = $(group).find("[data-animation]");
  });
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>
<div class="group">
    <div data-animation="test" class="test">Test</div>
    <p data-animation="test" class="test">Test</p>
    <span data-animation="test" class="test">Test</span>
</div>

Make sure the preview is small enough that there's space to scroll, then inspect one of the "test" elements and scroll up and down. You'll see that in Firefox, it adds and removes an ID of null to the test elements as you scroll:

null ID

In Safari, it happens less consistently – but when it does, the ID will start with sizzle.

If I change the .find("[data-animation]") to .find(".test"), it doesn't happen.

Given the sizzle ID that sometimes appears in Safari, I'm guessing this is due to an error in Sizzle (jQuery's selector engine) itself, and not something I'm doing incorrectly in my own code?


Solution

  • It seems that the creation of those empty identifiers is something that happens on Firefox only. But all browsers I checked with appear to have something similar going on, be it less visible. With Chrome and Opera you can see an active change on the parent div, without any final effect as a result. With IE it's very subtle, nothing is really noticeable in the DOM tree but there is still a light flicker in the style window. Indicating that it is responding to the same thing as well.

    When I dug around a bit these quotes from the jQuery documentation about the arguments that can be passed to the .find() method seemed to hold the best clue :

    A string containing a selector expression to match elements against.

    An element or a jQuery object to match elements against.

    https://api.jquery.com/find/

    I interpreted this as that you can't directly pass a data attribute but instead the approach would have to be to filter the elements themselves that contain it. The fix would then be quite simple. The culprite :

    .find("[data-animation]");
    

    And wrapping it in a jQuery object makes the functionality work :

    .find($("[data-animation]"));
    

    That actually solved the issue but the assumption was incorrect. Using a data attribute should qualify as a selector expression. The OP should feel free to accept another answer if that can provide a full explanation for the effect this query has on the parent. So far I have only noticed the following :

    • It is not just an issue with data but occurs with all attributes
    • Related to using classes, an element with an id does not get affected
    • Does not have anything to do with using the .each() loop at least
    • Probably the oddest... there is no such issue when using .children() instead

    That last one is quite baffling since both methods are very similar. But scouring the documentation I did find a difference, only .find() has a selector context and this looks to be at the root of it.

    Here is a strange example where the null id appears on body if the context is set to that :

    Demo

    And it disappears altogether when the second parameter is omitted...


    A working example of the original code, including some other minor tweaks :

    Pen

    var groups = $('.group');
    
    $(window).on('scroll resize orientationchange', showGroup);
    
    function showGroup() {
    
      groups.each(function() {
    
        var group = $(this),
        elements = group.find($('[data-animation]'));
      });
    }