Get the element which is the most visible on the screen

TLDR:

Inspired by this question and the necessity for similar functionality in my own projects, I've written a module/jQuery plugin based on the code below. If you're not interested in the 'how', just download that or install with your favourite package manager.

Original Answer:

The answer provided by exabyssus works well in most cases, apart from when neither of an element's top or bottom is visible e.g when the element height is greater than the window height.

Here's an updated version which takes that scenario into account and uses getBoundingClientRect which is supported right the way down to IE8:

// Usage: var $element = getMostVisible($('.elements' ));
function getMostVisible($elements) {
    var element,
        viewportHeight = $(window).height(),
        max = 0;

    $elements.each(function() {
        var visiblePx = getVisibleHeightPx($(this), viewportHeight);

        if (visiblePx > max) {
            max = visiblePx;
            element = this;
        }
    });

    return $elements.filter(element);
}

function getVisibleHeightPx($element, viewportHeight) {
    var rect = $element.get(0).getBoundingClientRect(),
        height = rect.bottom - rect.top,
        visible = {
            top: rect.top >= 0 && rect.top < viewportHeight,
            bottom: rect.bottom > 0 && rect.bottom < viewportHeight
        },
        visiblePx = 0;

    if (visible.top && visible.bottom) {
        // Whole element is visible
        visiblePx = height;
    } else if (visible.top) {
        visiblePx = viewportHeight - rect.top;
    } else if (visible.bottom) {
        visiblePx = rect.bottom;
    } else if (height > viewportHeight && rect.top < 0) {
        var absTop = Math.abs(rect.top);

        if (absTop < height) {
            // Part of the element is visible
            visiblePx = height - absTop;
        }
    }

    return visiblePx;
}

This returns the most visible element based on pixels rather than as a percentage of the height of the element, which was ideal for my use-case. It could easily be modified to return a percentage if desired.

You could also use this as a jQuery plugin so you can get the most visible element with $('.elements').mostVisible() rather than passing the elements to the function. To do that, you'd just need to include this with the two functions above:

$.fn.mostVisible = function() {
    return getMostVisible(this);
};

With that in place you can chain your method calls rather than having to save the element into a variable:

$('.elements').mostVisible().addClass('most-visible').html('I am most visible!');

Here's all of that wrapped up in a little demo you can try out right here on SO:

(function($) {
  'use strict';

  $(function() {
    $(window).on('scroll', function() {
      $('.the-divs div').html('').removeClass('most-visible').mostVisible().addClass('most-visible').html('I am most visible!');
    });
  });

  function getMostVisible($elements) {
    var element,
      viewportHeight = $(window).height(),
      max = 0;

    $elements.each(function() {
      var visiblePx = getVisibleHeightPx($(this), viewportHeight);

      if (visiblePx > max) {
        max = visiblePx;
        element = this;
      }
    });

    return $elements.filter(element);
  }

  function getVisibleHeightPx($element, viewportHeight) {
    var rect = $element.get(0).getBoundingClientRect(),
      height = rect.bottom - rect.top,
      visible = {
        top: rect.top >= 0 && rect.top < viewportHeight,
        bottom: rect.bottom > 0 && rect.bottom < viewportHeight
      },
      visiblePx = 0;

    if (visible.top && visible.bottom) {
      // Whole element is visible
      visiblePx = height;
    } else if (visible.top) {
      visiblePx = viewportHeight - rect.top;
    } else if (visible.bottom) {
      visiblePx = rect.bottom;
    } else if (height > viewportHeight && rect.top < 0) {
      var absTop = Math.abs(rect.top);

      if (absTop < height) {
        // Part of the element is visible
        visiblePx = height - absTop;
      }
    }

    return visiblePx;
  }



  $.fn.mostVisible = function() {
    return getMostVisible(this);
  }

})(jQuery);
.top {
  height: 900px;
  background-color: #999
}

.middle {
  height: 200px;
  background-color: #eee
}

.bottom {
  height: 600px;
  background-color: #666
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="the-divs">
  <div class="top"></div>
  <div class="middle"></div>
  <div class="bottom"></div>
</div>

Yes, this question is too broad. But I was interested on solving it. Here is crude example on how to accomplish it.

I tried to explain what's going on with comments. It surely can be done better, but I hope it helps.

// init on page ready
$(function() {
    // check on each scroll event
    $(window).scroll(function(){
        // elements to be tested
        var _elements = $('.ele');

        // get most visible element (result)
        var ele = findMostVisible(_elements);
    });
});


function findMostVisible(_elements) {

    // find window top and bottom position.
    var wtop = $(window).scrollTop();
    var wbottom = wtop + $(window).height();


    var max = 0; // use to store value for testing
    var maxEle = false; // use to store most visible element

    // find percentage visible of each element
    _elements.each(function(){

        // get top and bottom position of the current element
        var top = $(this).offset().top;
        var bottom = top + $(this).height();

        // get percentage of the current element
        var cur = eleVisible(top, bottom, wtop, wbottom);

        // if current element is more visible than previous, change maxEle and test value, max 
        if(cur > max) {
            max = cur;
            maxEle = $(this);
        }
    });

    return maxEle;
}

// find visible percentage
function eleVisible(top, bottom, wtop, wbottom) {

    var wheight = wbottom - wtop;

    // both bottom and top is vissible, so 100%
    if(top > wtop && top < wbottom && bottom > wtop && bottom < wbottom)
    {
        return 100;
    }

    // only top is visible
    if(top > wtop && top < wbottom)
    {
        return  100 + (wtop - top) / wheight * 100;
    }

    // only bottom is visible
    if(bottom > wtop && bottom < wbottom)
    {
        return  100 + (bottom - wbottom) / wheight * 100;
    }

    // element is not visible
    return 0;
}

Working example - https://jsfiddle.net/exabyssus/6o30sL24/


Possible with getBoundingClientRect()

added a jQuery plugin

Iterate through the seleted elements and check

  • is the element in viewport?
  • whats the element's visible height?
  • is the element the most visible one?

function getMostVisibleElement(selector) {
    var clientRect = null;
    var clientRectTop = 0;
    var maxVisibleHeight = 0;
    var visibleHeightOfElem = 0;
    var mostVisibleElement = null;
    var skipRest = false;

    var visibleElems = $(selector).each(function(i, element) {
        if (skipRest === false) {
            clientRect = element.getBoundingClientRect();
            clientRectTop = Math.abs(clientRect.top);

            if (clientRect.top >= 0) {
                visibleHeightOfElem = window.innerHeight - clientRectTop;
            } else {
                visibleHeightOfElem = clientRect.height - clientRectTop;
            }

            if (visibleHeightOfElem >= clientRect.height) {
                mostVisibleElement = element;
                skipRest = true;
            } else {

                if (visibleHeightOfElem > maxVisibleHeight) {
                    maxVisibleHeight = visibleHeightOfElem;
                    mostVisibleElement = element;
                }
            }

        }
    });
    return mostVisibleElement;
}

$(window).on('click', function() {
    var mostVisible = getMostVisibleElement('.my-container');
    $(mostVisible).addClass('highlighted');
    
    setTimeout(function() {
      $(mostVisible).removeClass('highlighted');
    }, 200);
    // alert(mostVisible.id)
});
.my-container {
  height: 100vh;
}

#a {
  background: #007bff;
}

#b {
  background: #28a745;
  height: 70vh;
}

#c {
  background: #ffc107;
}

#d {
  background: #17a2b8;
}

.highlighted {
  background: #dc3545 !important; 
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="my-container" id="a"></div>
<div class="my-container" id="b"></div>
<div class="my-container" id="c"></div>
<div class="my-container" id="d"></div>