27

I want to enable a lazy loading for the contents of my website.

Just like Jquery Image loading http://www.appelsiini.net/projects/lazyload that is valid only for images.

I want to do it for the content (DIV's).

Suppose we have a long page then i want to download the div as they becomes visible.

I will download the content using JSON or PageMethods. But i want the code that will execute the function for loading contents.

So whether we can somehow find this that div is visible only scrolling down.

Means i need to use some scroll events but dont know how.

Any help is appreciated.

Moons
  • 3,833
  • 4
  • 49
  • 82
  • 2
    How long are your pages that this would be a desirable feature? As a user I find it annoying because when the page loads the scrollbar gives a visual indication of how long the page is but then if you keep loading more content as I scroll the page keeps getting longer and longer. – nnnnnn Nov 19 '11 at 08:33
  • @nnnnnn actually what happens is that when we have too much dynamic content in the page ex (RSS feed) and content from other divs in that case i want to use JSON etc to load that div only when the user actually see the DIV. – Moons Nov 19 '11 at 08:56
  • Vanilla js contains now the IntersectionObserver: https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver API for handling this. Way better than using scroll events. But be aware that still not supported for IE11 and safari as of 08/Nov/2017 – CommonSenseCode Nov 08 '17 at 09:36

4 Answers4

35

I was looking for this to load advertising from my openX server only when the advertising should be visible. I'm using the iFrame version of openX which is loaded in a div. The answer here put me on my way to solving this problem, but the posted solution is a bit too simple. First of all, when the page is not loaded from the top (in case the user enters the page by clicking 'back') none of the divs are loaded. So you'll need something like this:

$(document).ready(function(){
   $(window).scroll(lazyload);
   lazyload();
});

Also, you'll need to know what defines a visible div. That can be a div that's fully visible or partially visible. If the bottom of the object is greater or equal to the top of the window AND the top of the object is smaller or equal to the bottom of the window, it should be visible (or in this case: loaded). Your function lazyload() might look like this:

function lazyload(){
   var wt = $(window).scrollTop();    //* top of the window
   var wb = wt + $(window).height();  //* bottom of the window

   $(".ads").each(function(){
      var ot = $(this).offset().top;  //* top of object (i.e. advertising div)
      var ob = ot + $(this).height(); //* bottom of object

      if(!$(this).attr("loaded") && wt<=ob && wb >= ot){
         $(this).html("here goes the iframe definition");
         $(this).attr("loaded",true);
      }
   });
}

I tested this on all major browsers and even on my iPhone. It works like a charm!!

mc_kaiser
  • 717
  • 8
  • 18
patrick
  • 11,519
  • 8
  • 71
  • 80
  • Instead of the content being shown, I am getting 'here goes the iframe definition'. I am new to web developing so it will be really helpful if you point if out what I am doing wrong. – Wizard Sultan Feb 28 '13 at 23:53
  • 3
    well, that means it's working... you need to replace that with "); – patrick Mar 11 '13 at 10:42
  • in fact, you could use this for all kinds of content, not just an iframe. You can also put an image def in there: $(this).html("");, etc... be creative ;-) – patrick Mar 11 '13 at 10:45
  • 1
    Whilst this will work, it's inefficient as you continue to calculate offsets etc even once the images have loaded. Ideally you should unbind the scroll event once loaded. – Chris Haines Jun 02 '14 at 16:05
  • You also ought to "debounce" the function, e.g. http://davidwalsh.name/javascript-debounce-function – Chris Haines Jun 02 '14 at 16:06
  • @Hainesy, the way I use it is add attribute 'loaded=1' to loaded content and update the selector inside the function lazyload to only load content with .ads[loaded!=1] – patrick Jun 03 '14 at 14:07
  • @patrick, that solves part of it but you're still continuing to run the lazyload function every time the user scrolls, even once every element has been loaded. Ideally, you would unbind the scroll method once every element has loaded. See my answer below for an example. – Chris Haines Jun 06 '14 at 10:08
  • @Hainesy: I don't agree with that! In fact, I highly disagree... you can lazy load content which itself has lazy loading content. Endless scrolling for instance. So it highly depends on what it is you're lazy loading if you can afford to unbind or not... – patrick Jun 06 '14 at 13:02
  • @patrick - good point, but you can check for that. The point is that you are evaluating the window scroll position and height every time the user scrolls, unnecessarily in the instance that there are no lazy items remaining. – Chris Haines Jun 09 '14 at 08:26
  • 2
    Can you provide a fiddle? – Shikhar Jan 29 '15 at 07:46
22

The code below does not cover cases where the user scrolls up from the bottom (read patrick's comment below). Also, it allows multiple event executions because of several concurrent onscroll events (in most browsers you won't see this, most of the time).

$(document).ready(function(){
    $(window).scroll(function() {
        //check if your div is visible to user
        // CODE ONLY CHECKS VISIBILITY FROM TOP OF THE PAGE
        if ($(window).scrollTop() + $(window).height() >= $('#your_element').offset().top) {
            if(!$('#your_element').attr('loaded')) {
                //not in ajax.success due to multiple sroll events
                $('#your_element').attr('loaded', true);

                //ajax goes here
                //in theory, this code still may be called several times
            }
        }
    });
});

Proper solution, that takes into consideration scrolling from bottom here.

mc_kaiser
  • 717
  • 8
  • 18
Oroboros102
  • 2,214
  • 1
  • 27
  • 41
  • That solves my problem but as you scroll to the next downward div it keeps on hitting the function of upward div. Example if we have ten div then all the div's on top of it gets hit by this function – Moons Nov 19 '11 at 08:53
  • Okay, that's a tiny problem. Added attr check in my answer. You can optimize it by using some global object instead of attributes. – Oroboros102 Nov 19 '11 at 08:58
  • 2
    Not to promote my own post, but this solution has a problem. It only works when you scroll down from the top of a page. If a user visits a link and clicks 'back' somewhere down your page everything 'above' the screen still gets loaded. check the post below. Basically the same as Oroboros's, the way to determine visibility is a little different – patrick Sep 07 '12 at 08:59
  • Can you provide a fiddle? – Shikhar Jan 29 '15 at 07:47
3

You may consider way point library :)

http://imakewebthings.com/waypoints/api/waypoint/

Its use cases and api's are defined in above link

It is of 9 kb when compressed. It will add an additional -100 ms- 50ms timelag while loading page on 3g/ 4g

Edit :- It can be used standalone and it also supports all major frameworks.

Ravinder Payal
  • 2,884
  • 31
  • 40
  • 1
    Although I'm may have to be dubious about your 100ms lag statistics, waypoint is quite a nice library. – Matt Fletcher Dec 08 '15 at 10:14
  • but now internet is improved highly all over the world, so 9kb is not a big problem , so cheers........ – Ravinder Payal Aug 24 '16 at 23:45
  • @RavinderPayal Considering the average 3G speeds seem to be about 5-7Mb at the moment, and the minimum I can find on any map (India, 2007) is 1Mbps, 9kb should add literally no load time at all. According to Wikipedia, dial-up speeds in the 1990s were around 220kbps, which would load in about 41 milliseconds. What sort of internet do you use?! – Alexander Craggs Apr 17 '17 at 23:02
  • @PopeyGilbert The average speed of Internet in metros(india) is 2 mega bits per second. It becomes ~250kilo bytes per second while downloading. So, theoretically it'll add atleast 30-40 ms. Now, add browser compilation of script and network latency. It'll be more than >50ms. And in remote areas in India even on 4g, it hardly gives more than 4mbps. But, the features provided by library are far good than the minute initial loading delay. – Ravinder Payal Apr 18 '17 at 04:25
1

Here is a solution that lazy loads images when they come within 500px of view. It can be adapted to load other types of content. The images themselves have an attribute data-lazy="http://..." with the image url in it, and then we just put a dummy transparent image for the src attribute.

var pixelLoadOffset = 500;
var debouncedScroll = debounce(function () {
    var els = app.getSelector(el, 'img[data-lazy]');
    if (!els.length) {
        $(window).unbind('scroll', debouncedScroll);
        return;
    }
    var wt = $(window).scrollTop();    //* top of the window
    var wb = wt + $(window).height();  //* bottom of the window

    els.each(function () {
        var $this = $(this);
        var ot = $this.offset().top;  //* top of object
        var ob = ot + $this.height(); //* bottom of object
        if (wt <= ob + pixelLoadOffset && wb >= ot - pixelLoadOffset) {
            $this.attr('src', $this.attr('data-lazy')).removeAttr('data-lazy');
        }
    });
}, 100);
$(window).bind('scroll', debouncedScroll);

The debounce function I'm using is as follows:

function debounce(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this, args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
        }, wait);
        if (immediate && !timeout) func.apply(context, args);
    };
}

You need to keep in mind that this isn't very effective on iOS, as the scroll event doesn't fire until after the user has finished scrolling, by which time they will already have seen the blank content.

Chris Haines
  • 6,445
  • 5
  • 49
  • 62