JavaScript Detecting when iFrame Scrolled to Bottom

Page content

Objective

This situation is becoming more and more common as people do more and more commerce online. I have a customer who wants ensure that their customers (online) have scrolled to the bottom of a document to make sure that their customers have read the terms, policy, contract, agreement or whatever it might be.
Depending on the requirements, this can be handled differently. We have all seen them on pages, and we even see this now when signing up for something. Some companies have a long scrolling page and just care about when that “agreement” is in the viewport. Others have a button to open it in a new window, tab or model, and that will suffice even if they never click over to that or immediately close out of it. This requirement has the agreement on the page, its pretty much the only thing on the page and takes up the majority of real estate as it should, and is already in view. This requirement wants users to scroll to the bottom of the document, before being able to proceed.

Assumptions

Based on the requirement and what I have to work with, there are a few assumptions. First

function checkRAScroll() {
  var iframe = $("iframe#agreement").contents();

  // called only when the iframe is scrolled (as opposed to the rest of the window - possibly due to someone on a mobile device or scrolling smaller)
  $(iframe).scroll(function() {
    // I only want to get new variables when the iFrame is scrolled
    var iframeContentsHeight = parseInt(
      $("iframe##agreement")
        .contents()
        .height()
    );
    var iframeHeight = parseInt($("iframe#agreement").height());
    var iframeContentsScrollTop = parseInt(
      $("iframe#agreement")
        .contents()
        .scrollTop()
    );
    var signAreaHeight = parseInt(
      $("iframe#agreement")
        .contents()
        .find("span.signature")
        .height()
    );

    // Test if the signature area is within the scroll area
    if (
      iframeContentsHeight - iframeHeight - iframeContentsScrollTop <=
      signAreaHeight
    ) {
      // the "Next Button" (id labeled as nextBtn) is set to disabled in HTML initially
      $("#nextBtn").removeAttr("disabled", false);
    }
  });
}

The above is a bit verbose and so I tried a few different things. The parseInt() is not necessarily needed, but good as a check. Also, I am getting the height of the span.signature area. This particular document has a big blank area for the user to physically sign (if/when printed) that is that span.signature area. I just care about when the user gets to the bottom of the content, not including that blank signature area. That is in my if comparison.
First, the variables that aren’t based on the “contents” of the iFrame can be taken out of the scroll function. This scroll function is based on the contents of the iFrame, and its within the iFrame that the height and scrollTop can be identified. This means that only the height of the iFrame (not the height of the contents of the iFrame) can be taken out of that function.
I recommend testing this and seeing the values you get, and I got 2 very diferent values based on if its in the scroll function or not.
Personally, I will also often like to assign values to variables, 1) for explaination, 2) for quick reference later and 3) I think it makes more readable to myself and others later. However, this is not needed.

function checkRAScroll() {
  // variables that don't change and can be accurately accessed outside of the iframe
  var iframe = $("iframe#agreement").contents();
  var iframeHeight = parseInt($("iframe#agreement").height());

  // called only when the iframe is scrolled (as opposed to the rest of the window - possibly due to someone on a mobile device or scrolling smaller)
  $(iframe).scroll(function() {
    // I only want to get new variables when the iFrame is scrolled
    var iframeContentsHeight = parseInt(
      $("iframe##agreement")
        .contents()
        .height()
    );
    var iframeContentsScrollTop = parseInt(
      $("iframe#agreement")
        .contents()
        .scrollTop()
    );
    var signAreaHeight = parseInt(
      $("iframe#agreement")
        .contents()
        .find("span.signature")
        .height()
    );

    // Test if the signature area is within the scroll area
    if (
      iframeContentsHeight - iframeHeight - iframeContentsScrollTop <=
      signAreaHeight
    ) {
      // the "Next Button" (id labeled as nextBtn) is set to disabled in HTML initially
      $("#nextBtn").removeAttr("disabled", false);
    }
  });
}

The next part is that since I am defining the iframe contents as a variable named iframe, why not reuse it. That is half the reason I am using these variables right? For reusability.

function checkRAScroll() {
  // variables that don't change and can be accurately accessed outside of the iframe
  var iframe = $("iframe#agreement").contents();
  var iframeHeight = parseInt($("iframe#agreement").height());

  // called only when the iframe is scrolled (as opposed to the rest of the window - possibly due to someone on a mobile device or scrolling smaller)
  $(iframe).scroll(function() {
    // I only want to get new variables when the iFrame is scrolled
    var iframeContentsHeight = parseInt($(iframe).height());
    var iframeContentsScrollTop = parseInt($(iframe).scrollTop());
    var signAreaHeight = parseInt(
      $(iframe)
        .find("span.signature")
        .height()
    );

    // Test if the signature area is within the scroll area
    if (
      iframeContentsHeight - iframeHeight - iframeContentsScrollTop <=
      signAreaHeight
    ) {
      // the "Next Button" (id labeled as nextBtn) is set to disabled in HTML initially
      $("#nextBtn").removeAttr("disabled", false);
    }
  });
}

A caveat that tripped me for a little bit is / was the if statement.
It doesn’t seem like much, but just make sure that you have things in the correct order otherwise you might get a negative number or other unexepected results. I know, math. I knew the contents of the iframe would be the biggest, which is the document itself, hence why its scrollable/scrolling. So start with that, then substract the height of the area of the document being viewed. Why the height of the area, because that will help us identify when something is in view at the bottom of that area. Then subtract the result of the scrollTop function for the iframe contents and you should get what you want. The idea is that the combination of these 3 items should equal 0 (cancel each other out) when scrolled to the bottom of the iframe. The same concept can be applied for any scrollable area, such as a scrollable div.

What is the scrollTop value telling us?

Just went over “HOW to do this” and I used the scrollTop JavaScript function. But the question is, what is that value telling us? There is also a cousin method called scrollLeft for the horizontal scrollbar. If we output that value, we see the number associated with it. This may not seem like much, but put it into context of the height of the contents. This number, the scrollTop, tells us how far down the scrollable area is top of the scrollable area is.
For example…
IF the scrollable area is 500px high. The content is 2000px long. We can only see 500px height at a time in this area. If I was to scroll down on the page and stop at place so that the scrollTop value is 1200, that means that I can see the content of the document from 1200px to 1700px. 1200px plus the 500px viewable area equal 1700px, which would be the bottom of what is viewed. If there was a function called scrollBottom that would return 1700 in this case, but there is no function called that. Which also means there is 1200px of space of content above the topmost line in that scrollable area (or document) that I can’t see, and there is 300px of content below the scrollable area that we can’t see.
IF the scrollTop was to equal the height of the contents of the iframe, then somehow you magically scrolled past the bottom of the contents so that its at the top of the scrollable area, OR your viewable area for the scrollable area is 0.
A different way to look at the IF statement here is possibly to use that approach, but compare it to the height of the iframe (or whatever scrollable area). That would look something like this instead.

    if ( iframeContentsHeight - iframeContentsScrollTop <= iframeHeight) {

With this code, I also only care about the user scrolling to the bottom 1 time. If they scroll back to the top of the document, they can still continue. If they do another action on the page, and then come back to this segment, they can still continue. The concept is that they “scrolled” through it once, therefore they must have read it. Right?