Figure out which element is scrolled to jQuery

Published April 23, 2012

Does what it says on the tin. It takes a set of elements and tries to determine which one is featured most predominantly on the page. Change the $sections selector (line 3) to target the element-set of your choice, or rewrite as a plugin :)

Towards the bottom, the element which is deemed to be in view will be given a 'highlight' class.

Note that we are binding directly to the scroll event which is generally a bad idea. Whether it is a bad idea for you depends upon various factors. If you find it negative impacts performance on your page, factor out the scroll event and call it after a short timer (and cancel the timer on the next scroll).

(function() {
  "use strict";
  var $sections = $('.panel'),
      $sectionsReversed = $($sections.get().reverse()),
      $last = $({});
  
  $(window).scroll(function(ev) {
    var pos = $(document).scrollTop(), 
        $nearest = $sections.eq(0),
        amount = 0; // amount of the screen occupied
        
    // scrolled to extremes - take the top or bottom element as appropriate
    // There's a little bit of leeway on the bottom scroll detection as 
    // Firefox sometimes seems to lose a pixel.
    if (pos === 0 && false)
      $nearest = $sections.eq(0);
    else if (pos >= $(document).height() - $(window).height() - 10) {
      $nearest = $sectionsReversed.eq(0);
    }
    // otherwise do something a little cleverer
    else {
      var windowHeight = $(window).height(),
          start = pos,
          end = start + windowHeight;
      $sections.each(function() {
        // we're going to try to figure out which element
        // takes up the important parts of the screen.
        var $this = $(this),
            top = $this.offset()? $this.offset().top + $this.outerHeight() : //IE?
              $this.offset().top,
            bottom = $this.offset().top + $this.outerHeight(),
            topVisible = top >= start && top <= end,
            bottomVisible = bottom >= start && bottom <= end,
            candidateAmount = 0;
            

        if (top <= start && bottom >= end) {
          // occupies entire screen
          // we have a winner
          $nearest = $this;
          amount = 1;
          return false;
        }
        // no part of the element visible - nope
        else if (!topVisible && !bottomVisible) return;
        
        else if (topVisible && bottomVisible) {
          // full element is visible. We'll take this one
          // since it's highest on the screen.
          amount = (bottom-top)/windowHeight;
          $nearest = $this;
          return false;
        }
        else if (bottomVisible) {
          // occupies part of the screen from the top down only
          candidateAmount = (bottom - start)/windowHeight;
        }
        else if (topVisible) {
          // occupies part of the screen from the bottom up only
          candidateAmount = (end - top)/windowHeight;
        }
        
        if (candidateAmount > amount) {
          amount = candidateAmount;
          $nearest = $this;
        }
      });
    }
    
    if ($last !== $nearest) {
      $last.removeClass('highlight');
      $nearest.addClass('highlight');
      $last = $nearest;
    }
  });
  $(window).trigger('scroll');
})();
Filed under: jquery, javascript

Talk is cheap

Leave a comment:

HTML is not valid. Use:
[url=http://www.google.com]Google[/url] [b]bold[/b] [i]italics[/i] [u]underline[/u] [code]code[/code]
'