Search code examples
javascriptjquerygetboundingclientrect

getBoundingClientRect() returns same values for all children if first child has any negative value


The Context

I'm building an infinite horizontal scroll of images:

<div class="infinite-thumbs">
    <img src="1.jpg" class="thumb thumb-one">
    <img src="2.jpg" class="thumb thumb-two">
    <img src="3.jpg" class="thumb thumb-three">
    ...
    <img src="10.jpg" class="thumb thumb-ten">
</div>

<style lang="stylus">

    .infinite-thumbs
        position absolute
        width 100%
        height 180px
        bottom 40px
        white-space nowrap
        overflow auto
        overflow-y hidden

    .thumb
        position relative
        display inline-block
        width 200px
        height 180px

</style>

Learn more about Stylus here: stylus-lang.com


And then I've got some jQuery/JS to handle the cloning and appending of images when they're off-screen:

function scrollUpdate() {

    $('.thumb').each(function() {

        var bounding = $(this)[0].getBoundingClientRect();

        if (bounding.right < 0) {
            var $el = $(this);
            $el.clone(true).appendTo('.infinite-thumbs');
            $el.remove();
        }

    });

}

$('.infinite-thumbs').on('scroll', function () {
    window.requestAnimationFrame(scrollUpdate);
});

So scrollUpdate() loops over each of the .thumb elements and checks to see if it's visible on-screen. If it's not (bounding.right < 0) then it gets cloned and appended to the end of the .infinite-thumbs element.



The Problem

The problem I have is that once one of the .thumb elements returns a negative value for bounding.right all the .thumb elements return the exact same set of bounding values.

So when all are visible, I get this in my console:

.thumb-one: { top : 0, right : 200, ... }
.thumb-two: { top : 0, right : 400, ... }
.thumb-three: { top : 0, right : 600, ... }
...
.thumb-ten: { top : 0, right : 2000, ... }

But as soon as the first child element (.thumb-one) obtains a negative bounding.right value, I get this in my console:

.thumb-one: { top : 0, right : -1, ... }
.thumb-two: { top : 0, right : -1, ... }
.thumb-three: { top : 0, right : -1, ... }
...
.thumb-ten: { top : 0, right : -1, ... }

What gives? Why would they all return a bounding object with the exact same values just because one of them is off-screen?

Anyone have any idea what's going on here?



NOTE:

Both $.fn.offset() and $.fn.position() behave in the same way as getBoundingClientRect(); they return the same set of values for each .thumb once .thumb-one has a negative value in its result.


Solution

  • It happens because you remove the element before check all the thumbs' positions. Removing the first element causes that the next element becomes the first, going off-screen. This way, every thumb will assume the same 'right' position.

    The solution Create a temporary array outside the 'each' cycle and use it to save the off-screen thumbs. Then, after the cycle, clone, remove and append elements in the same way as before. Something like this:

    function scrollUpdate() {
        var offScreenElements = [];
        $('.thumb').each(function() {
    
            var bounding = $(this)[0].getBoundingClientRect();
    
            if (bounding.right < 0) {
                offScreenElements.push($(this));
            }
        });
        $.each(offScreenElements, function(index, element) {
            element.clone(true).appendTo('.infinite-thumbs');
            element.remove();
        });
    }