Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the height of content loaded through AJAX infinity scroll not measured correctly after loading?

I am loading posts composed of pictures or videos into the main div of my website with infinity scroll (implemented through AJAX). The system works pretty much like on any popular meme website.

The Problem: Each post has a body section on the left and a social button container on the right. I want the social button container to move alongside the body of the post as the user scrolls, and stop at the bottom of the parent div so that they can like it when they reach the bottom of the post. The problem is that some of the posts loaded through my AJAX scroll function don't seem to calculate the parents height properly and they either never move or jump straight to the bottom of the parent div when the top of the browser window reaches them. Here is a visual illustration of the desired behavior.

Implementation: I wasn't able to compile a working jsFiddle that would simulate the behavior of the AJAX requests loading images and videos combined with the on "scroll" calls. Here is my implementation and debugging attempts:

Each article has the following structure:

<div class="article-container">
   <div class="article-body-left"><img src="..."></div>
   <div class="social-buttons-right">
      <div class="facebook-button">...</div>
   </div>
</div>

After each AJAX request is complete I try to use on 'scroll' to determine each of the social button containers positions and modify them as the user scrolls using ajaxComplete():

$(document).ajaxComplete(function() {
  bind_sticky_boxes();
}); 

function bind_sticky_boxes() {
  $(window).on('scroll', function() {
    $('.social-buttons-right').not('.following').each( function() { 
        var element = $(this).addClass('following'),
        originalY = element.offset().top,   
        parent_offset = element.parent().offset().top,
        parent_height = element.parent().height(),
        element_height = element.height(),
        destination = originalY+parent_height-element.height() -10;

          $(window).on('scroll', function(event) {
              var scrollTop = $(window).scrollTop();
              if (scrollTop > originalY - 10){
                 element.css('position','absolute');
                 element.css('top',(scrollTop - parent_offset + 10) + 'px');
                 element.css('right', '0px');
                 if (scrollTop > (destination -10 ) ) {
                      element.css('top',parent_height-element_height);
                      element.css('right', '0px'); 
                      element.css('bottom', '0px');
                 }
              } else {
                 element.css('position','relative');
                 element.css('top','initial');
                 element.css('left', 'initial');
                 element.css('right','initial');
                 element.css('bottom', 'initial');
              } 
          });
    });
    });
}

Debugging and notes: The scroll function works fine after the first AJAX request, but somewhat breaks afterwards as some of the .social-buttons-right don't follow along the article body or jump straight to the bottom of the parent div during site scroll.

When I inspected the elements that are misbehaving I found, that they all have the ".following", meaning that the scroll function has found them, but they also get a bad mix of the css properties that bind_sticky_boxes gives them.

This lead me to believe that it has something to do with height calculation during the image load, so I used requestanimationframe() to wait a little and then setTimeout(function(){..},2000) to trigger the scroll bind even later. None of these really resolved the issue (the latter one wasn't a solution anyway.) I have also tried to trigger the AJAX requests a bit sooner, but that seemed to have an even worse effect for some reason.

Update: I am now convinced that the issue is related to the parent height calculation. I swapped the css properties for unique classes with the same attributes and noticed that the class change for misbehaving elements occurs almost always at the start of the parent div, which I believe to be a strong indication that it's height is not being calculated properly right after appending it to the main div.

In regards to my infinity scroll. I am not including the whole AJAX as I don't think it is strictly related to the issue. But I append the content to the main div this way:

success: function(response){
            if ( max_page >= paged){
                $('#page').append(response.content);
            } else{
                has_content = false;
            }
            loading = false;
          }

After the page is done loading I initiate my infinity scroll. AJAX requests trigger when user reaches the element with id load-more-posts, which is below the main wrapper.

$(window).on('load',function(){
 $(window).on('scroll', function() {
      var element_position = $('#load-more-posts').offset().top + $('#load-more-posts').outerHeight() - window.innerHeight;
      var y_scroll_pos = $(window).scrollTop();
      if( ( y_scroll_pos >= element_position ) && !loading) {
          paged++;
          load_more_posts();
      }
  });    

});

like image 457
PeterTheLobster Avatar asked Dec 22 '15 06:12

PeterTheLobster


1 Answers

The problem is that currently the calculation of the various height/position properties is bound to the scroll event, which could fire before the images appended through AJAX are loaded and would therefor be calculated incorectly. To solve this problem I had to bind it to the load event, to make sure that the images are ready to be used.

$('.article-body-left img').one('load', function() {
    var element = $(this).parent().parent().find('.social-buttons-right'),
    originalY = element.offset().top,
    parent_offset = element.parent().offset().top,
    parent_height = element.parent().height(),
    element_height = element.height(),
    destination = parent_offset+parent_height-element.height() -10;
    //alert("Element offset: "+originalY+"\nParent offset: "+parent_offset+"\nParent height: "+parent_height+"\nElement height: "+element_height+"\nDestination: "+destination);
      $(window).on('scroll', function(event) {
          var scrollTop = $(window).scrollTop();
          if (scrollTop > originalY - 10){
             element.removeClass('above').removeClass('below').addClass('along');
             element.css('top',(parseInt(scrollTop - parent_offset + 10)) + 'px');
             if (scrollTop > (destination -10 ) ) {
                  element.removeClass('above').removeClass('along').addClass('below');
                  element.css('top','');
             }
          } else {
            element.removeClass('below').removeClass('along').addClass('above');
            element.css('top','');
          }
      });
}).each(function() {
  if(this.complete) $(this).load();
});

CSS:

.along{
   position: absolute;
   right : 0px;
}

.below{
    position: absolute;
    right: 0px;
    bottom: 0px;
}
.above{
  position: relative;
}
like image 191
PeterTheLobster Avatar answered Oct 31 '22 03:10

PeterTheLobster