0

JSFiddle: https://jsfiddle.net/1fLmtyoj/15/

I'm following this example to see if an element is overflowing, but something isn't working, this is for a DIV: Check with jquery if div has overflowing elements and https://stackoverflow.com/a/7668692/1005607

I have an <LI> tag containing <SPAN>s. From the above answer, it's necessary to check (1) Overflow active, (2) Semi-visible parts. I combined them into the following function.

function isOverflowing(element) {

  // 1. Main Part for overflow check
  if (element.offsetHeight < element.scrollHeight ||
    element.offsetWidth < element.scrollWidth) {
    return true;
   }  

  // 2. Partially invisible items
  var invisibleItems = [];
  for(var i=0; i<element.childElementCount; i++){
    if (element.children[i].offsetTop + element.children[i].offsetHeight >
        element.offsetTop + element.offsetHeight ||
        element.children[i].offsetLeft + element.children[i].offsetWidth >
        element.offsetLeft + element.offsetWidth ){

          invisibleItems.push(element.children[i]);
      }

  }

  if (invisibleItems.length) {
    return true;
  }
  // Otherwise, neither Part 1 nor 2, return FALSE
  return false;
}

In the JSFiddle, the expected output is that #1 and #3 are not overflowing, but #2 and #4 are overflowing. But all 4 are shown as not overflowing.

gene b.
  • 10,512
  • 21
  • 115
  • 227
  • 1
    In short, your selector `$('li:eq(0)') ` is returning an array. You'll need to look at `element[0]` -- for example, `element[0].childElementCount` returns what you're looking for. – Snowmonkey Nov 02 '17 at 19:33

1 Answers1

1

If you were to display element via the console, you would see that element does not have the attribute childElementCount -- but the first member of the element array does. Thus, refer to element[0] for all the attributes, and it behaves as expected. I think this is due to the selector you've used.

function isOverflowing(element) {

  // 1. Main Part for overflow check
  if (element.offsetHeight < element.scrollHeight ||
    element.offsetWidth < element.scrollWidth) {
    return true;
   }  
  
  // 2. Partially invisible items
  var invisibleItems = [];
  for(var i=0; i<element[0].childElementCount; i++){

  if (element[0].children[i].offsetTop + element[0].children[i].offsetHeight >
        element[0].offsetTop + element[0].offsetHeight ||
        element[0].children[i].offsetLeft + element[0].children[i].offsetWidth >
        element[0].offsetLeft + element[0].offsetWidth ){

          invisibleItems.push(element[0].children[i]);
      }

  }
  
  if (invisibleItems.length) {
   return true;
  }
  // Otherwise, neither Part 1 nor 2, return FALSE
  return false;
}

$('#result').html('#1? ' + isOverflowing($('li:eq(0)')) + 
       '#2? ' + isOverflowing($('li:eq(1)')) + 
       '#3? ' + isOverflowing($('li:eq(2)')) + 
       '#4? ' + isOverflowing($('li:eq(3)')) 
);
.type1 {
  border: 1px solid black;
  width: 130px;
  height: 30px;
  margin-bottom: 5px;
}

.type2 {
  border: 1px dotted red;
  width: 50px;
  height: 25px;
  margin-bottom: 45px;
}

.type3 {
  border: 1px dashed blue;
  width: 200px;
  height: 40px;
  margin-bottom: 5px;
}

.type4 {
  border: 1px dashed green;
  width: 100px;
  height: 10px;
  margin-bottom: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>

  <li class="type1"><span>Some text in LI 1</span></li>
  <li class="type2"><span>Some more text in LI 2</span></li>
  <li class="type3"><span>A long string of text in LI3</span></li>
  <li class="type4"><span>A much longer string of text in LI4</span></li>

</ul>

<br/>
<br/>
<br/>
<br/>
<p id="result"></p>

As you say, however, this solution isn't generic enough -- it doesn't allow for nested DOM nodes. To fix this, we would want a recursive function. Try this, possibly. It's working, I think, but it really made my brain hurt to think recursively. ;)

function isOverflowing(element) {
  /*******
   * This is going to be a recursive function -- first, 
   *   it'll check if there are children elements and,
   *   if not, simply return true. In the event there 
   *   are child elements, it should recurse over each
   *   to see if any child overflows, whether partially
   *   invis or not.
   ******/
  var elOverflows;
  var el = element[0];

  // On the first iteration, we initialize these. On every
  //  recursive iteration, we simply preserve them
  var mainHeight = el.offsetTop + el.offsetHeight,
    mainOffsetHeight = el.offsetHeight,
    mainWidth = el.offsetLeft + el.offsetWidth,
    mainOffsetWidth = el.offsetWidth;

  // 1. Main Part for overflow check
  if (mainOffsetHeight < el.scrollHeight ||
    mainOffsetWidth < el.scrollWidth) {
    elOverflows = true;
    return elOverflows;
  }

  /***
   *  2. If the current element doesn't contain any
   *     children, and the above check didn't return,
   *     then we don't have any overflow. 
   ***/
  if (el.childElementCount == 0) {
    elOverflows = false;
    return elOverflows;
  } else {
    // Here, we have child elements. We want to iterate
    //  over each of them and re-call isOverflowing() on
    //  each one. This is the recursion, allowing us to
    //  have any number of nested child elements.
    $(el.children).each(function() {
      elOverflows = isOverflowing($(this));
    });

    return elOverflows;

  }

}

$("#container li").each(function() {
  $("#result").append("<p>#" + $(this).index() + ": " + isOverflowing($(this)));
})
.type1 {
  border: 1px solid black;
  width: 130px;
  height: 30px;
  margin-bottom: 5px;
}

.type2 {
  border: 1px dotted red;
  width: 50px;
  height: 25px;
  margin-bottom: 45px;
}

.type3 {
  border: 1px dashed blue;
  width: 200px;
  height: 40px;
  margin-bottom: 5px;
}

.type4 {
  border: 1px dashed green;
  width: 100px;
  height: 10px;
  margin-bottom: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id='container'>
  <li class="type1"><span>Some text in LI 1</span></li>
  <li class="type2"><span>Some more text in LI 2</span></li>
  <li class="type3"><span>A long string <span>containing A much longer string </span> in its center bit.</span>
  </li>
  <li class="type4"><span>A much longer string of text in LI4</span></li>

</ul>

<br/>
<br/>
<br/>
<br/>
<p id="result"></p>

I've made isOverflowing recursive, and for each child node of the current node, I simply re-call it again, until there are no more child nodes. passing the return value out to the initial call allows us to see if any of the child nodes exceed the bounds of the initial node.

Hope this helps!

Snowmonkey
  • 3,716
  • 1
  • 16
  • 16
  • This general solution won't work if we don't make assumptions about the structure of the parent, though. Suppose I had nested tags inside an LI -- then the whole children[i] traversal would fail. I suppose I was wondering if there was a generic way to check for ANY children/sub-children overflowing the parent. – gene b. Nov 03 '17 at 14:10
  • @geneb. -- try that one on for size. To do what you want to do, you're going to be looking at recursion through all the child DOM nodes. I've rewritten the isOverflowing to support that. Best regards! – Snowmonkey Nov 20 '17 at 18:07