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

1

8

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();
      }
  });    

});

Stickleback answered 22/12, 2015 at 6:41 Comment(8)
@Mayank I cannot even get the layout right. jsFiddle adds some sort of margin that I cannot get rid of. I want .post-body to be left and .post-social to be right, but there is a margin in .post-body that I cannot remove.. Both are inside .post-container... not sure why the margin is there jsfiddle.net/hu0om6d3/4Stickleback
I don't see issues with your AJAX, I see issues with your CSS though. Can you post an image showing how you want it to look like?Palpable
@LGSon Sorry gotta go for christmas dinner.. will post more tomorrow. I will just add that I put the css inside separate classes and swap those around so that they don't mix and that works well for the first ajax request. Its just that after I append new posts in following requests the parent height seems to be calculated badly, as if there was no image inside it and the social button container just jumps to the end of the parent. Hence why I guess its something to do with image loading. I will improve the question with more detail tomorrow.Stickleback
@LGSon I have added a simple image illustrating the desired behavior in the problem section of the div.Stickleback
Checked your image and reread your question. It appears to me that it might be that the element reference is pointing to the wrong post when the calculation occur, as when you say it either stays at top or jumps to bottom. That would be the expected behavior if it referenced an element above or below the one in view. This might help to debug and if you want further help, a working snippet is necessary to see what is going on.Palpable
@LGSon I was just doing further testing and used alert to print out all the values after the calculations occur. Basically what happens is that when the post image is not chached or takes longer to load, the parents height = social-buttons-right, because the image is not loaded yet and has no height. And because the point where the social-buttons element stops moving = parent.offset+parent.height-social.height, the stop point essentially becomes the top of the parent container. This is because the content isnt loaded yet so parent height = just the height of the social-buttons container.Stickleback
With that knowledge you now know how to fix it, right? .. anyway, here is a post that might shed some light on knowing when an image is loaded: #822016Palpable
@LGSon Yes thanks I figured that I need to handle the load event for images and videos now. But there might be text posts as well so I need to figure that out too.Stickleback
S
3

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;
}
Stickleback answered 26/12, 2015 at 18:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.