6

Let say I have the following HTML

<div id="someContainer" style="overflow:hidden; height:300px">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  ....(countless of similar items)
</div>

How do I detect the first item that is hidden in this case using JQuery or JS? Lots of solutions given on stackOverflow does not work for the case with parent element that has overflow:hidden

3
  • Without more layout knowledge, I don’t think there’s a nice way.
    – Ry-
    Commented Jun 11, 2017 at 5:42
  • You could count the hight of the children and calculate if it's hidden based on it's position to it's siblings and the hight of the parent.
    – Darkrum
    Commented Jun 11, 2017 at 5:54
  • Possible duplicate stackoverflow.com/questions/7668636/…
    – Darkrum
    Commented Jun 11, 2017 at 6:00

3 Answers 3

2

EDIT: my compulsion forced me into changing this code without jQuery and correcting a bug

Here's a jsFiddle for a quick test, I repeat the code below:

HTML:

<div id="someContainer" style="overflow:hidden; position:relative; height:26px; background-color: #ffffff;">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
<pre id="output"></pre>

JS (no jQuery):

var i, top, container, output, height;

container = document.getElementById("someContainer");
output = document.getElementById("output");
height = container.offsetHeight;

output.innerText += "Height: " + height + "px\n";

for (i = 0; i < container.children.length; i++)
{
  top = container.children[i].offsetTop;
  output.innerText += "Top " + i + ": " + top + "px => visible=" + (top < height) + "\n";
}

The output will be:

Height: 26px
Top 0: 0px => visible=true
Top 1: 18px => visible=true
Top 2: 36px => visible=false

The first two items are visible (at least in part, I cut the B in half on purpose), while the last item is not visible. It falls beyond the lower edge of the container.

NOTE: I had to add position: relative; to the container so that it becomes a positioning reference for child elements. Otherwise, offsetTop would compute incorrectly under specific circumstances (depending on outer HTML).

1

Just incase someone is looking for a slightly more flexible answer to finding if an element is hidden by a parent with overflow, this will look at all of the parents of element and check if it is hidden or partially hidden for all of them. It returns a decimal where 1 is completely visible and 0 is completely hidden, and anywhere inbetween. I found it was useful to test if a dropdown list was partially hidden and should be popped up instead of down.

It uses JQuery, for the most part, but underscore was also helpful. This is not optimised at all, so...

function recursivePartialIsVisible(element: HTMLElement): number {
    const elementBoundary = {
        top: $(element).offset().top,
        left: $(element).offset().left,
        bottom: $(element).offset().top + $(element).height(),
        right: $(element).offset().left + $(element).width(),
        height: $(element).height(),
        width: $(element).width()
    };


    const visibility = _.chain($(element).parents())
        .map(parent => $(parent))
        .filter(parent => 
            parent.css('overflow') === 'hidden' || 
            parent.css('overflow') === 'scroll' || 
            parent.css('overflow') === 'auto' || 
            parent.css('overflow-y') === 'hidden' || 
            parent.css('overflow-y') === 'scroll' || 
            parent.css('overflow-y') === 'auto'
        )
        .map(parent => {
            const parentBoundary = {
                top: parent.offset().top,
                left: parent.offset().left,
                bottom: parent.offset().top + parent.innerHeight(),
                right: parent.offset().left + parent.innerWidth(),
                height: parent.innerHeight(),
                width: parent.innerWidth()
            };
            if(
                elementBoundary.bottom < parentBoundary.top || 
                elementBoundary.top > parentBoundary.bottom ||
                elementBoundary.left > parentBoundary.right ||
                elementBoundary.right < parentBoundary.left) {
                    return 0;
                }
            
            const areaOverlap = 
                (Math.max(elementBoundary.left, parentBoundary.left) - Math.min(elementBoundary.right, parentBoundary.right)) 
                * 
                (Math.max(elementBoundary.top, parentBoundary.top) - Math.min(elementBoundary.bottom, parentBoundary.bottom));

            const percentageOverlap = areaOverlap / (elementBoundary.height * elementBoundary.width);

            return percentageOverlap;
        })
        .reduce((previous, current) => previous * current, 1)
        .value();

    
    return visibility;
}

Note: the maths were borrowed from here

0

Here is a simple approach, which will loop through the container's children, and stop at the first node meeting the conditions

el.offsetTop > parent.offsetHeight + parent.scrollTop;

function find_firstHidden(container, full) {
  var items = container.querySelectorAll('*'); // get all nodes
  var maxTop = container.scrollTop + container.offsetHeight; // get the container's maxTop
  var found;
  for (var i = 0; i < items.length; i++) {
    // if we want to get the first element truncated, add its offsetHeight in the condition
    var o_top = full ? items[i].offsetTop + items[i].offsetHeight : items[i].offsetTop;
    if (o_top > maxTop) {
      found = items[i];
      return found;
    }
  }
  return null;
}
// set the element before the first completely hidden node red
find_firstHidden(someContainer)
  .previousElementSibling.style.background = 'red';
// set the element before the first partially hidden node green
find_firstHidden(someContainer, true)
  .previousElementSibling.style.background = 'green';
.item {
  height: 50px;
  border: 1px solid;
}
<div id="someContainer" style="overflow:hidden; height:300px">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>

Note that if ever you're gonna deal with an huge list of nodes, then a TreeWalker could probably give better perfs (same conditions would be applied)

Not the answer you're looking for? Browse other questions tagged or ask your own question.