6

I have container with Custom content scroller jQuery custom content scroller: This code:

(function($){
    $(window).load(function(){
        $(".content").mCustomScrollbar({
            scrollButtons: { enable: true },
            mouseWheelPixels: 250
        });
    });
})(jQuery);

And I would like to use it with Lazy Load:

$(function() {
  $("img.lazy").lazyload({
     effect : "fadeIn",
     container: $(".content")
  });
});

I think it should be working with callbacks function from Scroller page but I'm not good with jquery so my results are unsuccessful.

When I use my code like below it loads all images at page load:

$(function() {
  $("img.lazy").lazyload({
     effect : "fadeIn",
     container: $(".mCSB_container")
  });
});

The authos says this can be done by writing a simple js function and call it on whileScrolling event.

Thanks for any help.

HasanG
  • 12,734
  • 29
  • 100
  • 154
Jan Fryauf
  • 61
  • 1
  • 4

4 Answers4

5

Your attempt to use container does not work because mCustomScrollbar does not use scrolling containers, but relative positioning in a overflow: hidden container to achieve scrolling. The cleanest approach I can think of is to use a custom [event to trigger lazyload][1]. Here is how I'd do it for the example file given by Hasan Gürsoy:

<script>
$(document).ready(function () {
    var filledHeight = 250;
    $(window).load(function () {
        $(".scroll").height($(window).height() - filledHeight);
        $(".scroll").mCustomScrollbar({ scrollButtons: { enable: true },
        mouseWheelPixels: 250,
        callbacks: { whileScrolling: function() {
            var scroller = $(".mCSB_container");
            var scrollerBox = scroller.closest(".mCustomScrollBox");
            $(".scroll img.lazy").filter(function() {
                var $this = $(this);
                if($this.attr("src") == $this.data("src")) return false;
                var scrollerTop = scroller.position().top;
                var scrollerHeight = scrollerBox.height();
                var offset = $this.closest("div").position();
                return (offset.top < scrollerHeight - scrollerTop);
            }).trigger("lazyScroll");
            }}});

        $("img.lazy").lazyload({ event: "lazyScroll" });
    });
});
</script>

I also use a whileScrolling callback, but only to check which of the img.lazy images are visible. They are if their relative position to the container is not larger than the height of the container minus its CSS top property. (Assuming you always scroll top → down; this setup does not recognize images which are invisibile because you scrolled too far down.) For these images, the function then triggers the custom lazyScroll event, which is the event that lazyload uses to trigger loading images.

Note that this solution is not very portable yet: You'd have to replace the queries for the .mCSB_container and .mCustomScrollBox with ones that fetch you the elements associated with the current scroll box for the script to work in situations with more than one mCustomScrollbar. In a real-world scenario, I'd also cache the jQuery objects instead of recreating them on each invocation of the callback.

HasanG
  • 12,734
  • 29
  • 100
  • 154
Phillip
  • 13,448
  • 29
  • 41
2

I think the author meant additionally hooking into the whileScrolling event like this:

(function($){

    $(window).load(function(){

        $("#content_1").mCustomScrollbar({
            scrollButtons:{
                enable:true
            },
            callbacks:{
                whileScrolling:function(){ WhileScrolling(); } 
            }
        });

        function WhileScrolling(){
            $("img.lazy").lazyload();
        }

    });

})(jQuery);

where the HTML container is like the following:


<div id="content_1" class="content">
    <p>Lorem ipsum dolor sit amet. Aliquam erat volutpat. Maecenas non tortor nulla, non malesuada velit.</p>
    <p>
        <img class="lazy img-responsive" data-original="/img/bmw_m1_hood.jpg" width="765" height="574" alt="BMW M1 Hood"><br/>
        <img class="lazy img-responsive" data-original="/img/bmw_m1_side.jpg" width="765" height="574" alt="BMW M1 Side"><br/>
        <img class="lazy img-responsive" data-original="/img/viper_1.jpg" width="765" height="574" alt="Viper 1"><br/>
        <img class="lazy img-responsive" data-original="/img/viper_corner.jpg" width="765" height="574" alt="Viper Corner"><br/>
        <img class="lazy img-responsive" data-original="/img/bmw_m3_gt.jpg" width="765" height="574" alt="BMW M3 GT"><br/>
        <img class="lazy img-responsive" data-original="/img/corvette_pitstop.jpg" width="765" height="574" alt="Corvette Pitstop"><br/> 
    </p>
    <p>Aliquam erat volutpat. Maecenas non tortor nulla, non malesuada velit. Nullam felis tellus, tristique nec egestas in, luctus sed diam. Suspendisse potenti. </p>
    <p>Consectetur adipiscing elit. Nulla consectetur libero consectetur quam consequat nec tincidunt massa feugiat. Donec egestas mi turpis. Fusce adipiscing dui eu metus gravida vel facilisis ligula iaculis. Cras a rhoncus massa. Donec sed purus eget nunc placerat consequat.</p>
    <p>Cras venenatis condimentum nibh a mollis. Duis id sapien nibh. Vivamus porttitor, felis quis blandit tincidunt, erat magna scelerisque urna, a faucibus erat nisl eget nisl. Aliquam consequat turpis id velit egestas a posuere orci semper. Mauris suscipit erat quis urna adipiscing ultricies. In hac habitasse platea dictumst. Nulla scelerisque lorem quis dui sagittis egestas.</p> 
    <p>Etiam sed massa felis, aliquam pellentesque est.</p>
    <p>the end.</p>
</div>

To reduce the number of lazyload() during scrolling, we could use for example the mcs.top property for the scrolling content’s top position (pixels):

function WhileScrolling()
{
    // Debug:
    // console.log( 'top: ' + mcs.top );

    // Run lazyload in 10 pixel steps (adjust to your needs) 
    if( 0 == mcs.top % 10 )
    {
        // Run lazyload on the "div#conent_1" box:
        $("#content_1 img.lazy").lazyload();

        // Debug:
        //console.log( 'lazyload - mcs.top: ' + mcs.top );
    }
}

where we restrict the layzload selector, so we don't have to find all the images in the whole DOM tree.

I noticed that in Internet Explorer 11, the mcs.top can be floating numbers but it's alwyas whole integers in Chrome.

So we could floor it with Math.floor().

To further reduce the lazyload calls, we could additionally compare mcs.top to it's previous value:

var mcsTopPrev = 0;
var mcsTop = 0;
function WhileScrolling()
{
    // Fix the Chrome vs Internet Explorer difference
    mcsTop = Math.floor( mcs.top );

    // Current vs previous 
    if(  mcsTop != mcsTopPrev )
    {
        // Run lazyload in 10px scrolling steps (adjust to your needs)
        if( 0 == mcsTop % 10 )
        {
            $("#cphContent_lReferences img.lazy").lazyload();
        }
    }
    mcsTopPrev =  mcsTop;
}
birgire
  • 11,258
  • 1
  • 31
  • 54
  • It works but it is called many times and it modifies already loaded images too. – HasanG Mar 22 '14 at 17:58
  • Maybe we could call the `lazyload()` function once every `n` calls to the `WhileScrolling()`, using a counter? – birgire Mar 22 '14 at 18:10
  • There is probably a function to prevent lazyload refresh already loaded images. I'm now googling for it. – HasanG Mar 22 '14 at 18:17
  • Your setup works smooth on my Chrome, but I think you should replace `$(function () {` with `(function($){` to avoid the javascript error. – birgire Mar 22 '14 at 18:38
  • Thank you for noticing. I've replaced it with $(document).ready – HasanG Mar 22 '14 at 18:54
  • ps: when I check your current setup with the `trigger()` part instead of `lazyload()`, the images are not lazy loaded, when I view it in the "Network" panel of the *Chrome Developer Tools* after CTRL-F5. But I guess you are still playing with the page setup. Here is one way of decreasing the calls to `lazyload()`: [http://pastie.org/8959813](http://pastie.org/8959813) – birgire Mar 22 '14 at 21:04
  • Yes it loads the displayed area images and everything else on first scroll or page resize. – HasanG Mar 22 '14 at 23:54
  • The update is useful but scrolling still glitches in Firefox. Also IE11 unexpectedly doesn't load rest of the images on scroll. – HasanG Mar 23 '14 at 04:10
  • I found out why IE11 behaved differently than Chrome, due to different values of `mcs.top`. The **jQuery custom content scroller** behaves strange in my FireFox, even without the `whileScrolling()`callbacks. So I'm not sure the lazy load calls are responsable for these glitches in FireFox. – birgire Mar 23 '14 at 11:08
  • There is no need to call `lazyload()` multiple times. You only have to trigger the event which lazyload waits for when the images scroll into view. See my other answer. – Phillip Mar 25 '14 at 07:57
1

I am using plugins Lazy Load with Custom Content Scroller and had same issue.

What worked for me was to add a callback to the mCustomScrollbar's whileScrolling event, within which I trigger the scroll event of a container that I am lazyloading:

/* Lazy load images */
$(".blog-items img.lazyload").lazyload({
  effect: "fadeIn",
  effect_speed: 1000,
  skip_invisible: true,
  container: $(".blog-items")
});

/* Custom scrollbar */
$(".blog-items").mCustomScrollbar({
  theme: "rounded-dark",
  callbacks: {
    whileScrolling: function() {
      $(".blog-items").trigger("scroll");
    }
  }
});

I set the lazyload's property skip_invisible to true in the hope of increasing performance. Set to false if you experience any issues though.

humHann
  • 118
  • 1
  • 2
  • 7
  • I have tried to solve exactly this for the last week or so. I finally got it to work. Thanks a lot for this post. You made my day :-) – Peter Karlsson Nov 03 '20 at 13:37
0

Finally I wrote this that can be called from the mCustomScrollbar callback:

    function lazyLoad(selector,container) {     

      var $container=$(container);            
      var container_offset=$container.offset();
      container_offset.bottom=container_offset.top+$container.height();

      $(selector,$container).filter(function(index,img){
        if (!img.loaded) {                    
          var img_offset=$(img).offset();     
          img_offset.bottom=img_offset.top+img.height;
          return (img_offset.top<container_offset.bottom && (img_offset.top>0 || img_offset.bottom>0));
        }                                                                                                                                        
      }).trigger('appear');

    }

If the image size is constant and the image list is huge, maybe it could be worth searching for the first image using dichotomy then compute the range of indexes for which the 'appear' event must be triggered using the known image and container dimensions, and the offset() of the matching image...