Harry
Harry

Reputation: 54939

Check if an element's content is overflowing?

What's the easiest way to detect if an element has been overflowed?

My use case is, I want to limit a certain content box to have a height of 300px. If the inner content is taller than that, I cut it off with an overflow. But if it is overflowed I want to show a 'more' button, but if not I don't want to show that button.

Is there an easy way to detect overflow, or is there a better method?

Upvotes: 293

Views: 319044

Answers (18)

dashingdove
dashingdove

Reputation: 435

In case anyone else wanted an "overflow event listener", you can combine the other answers with the Resize Observer API to detect whenever an element begins overflowing.

      function isOverflown({clientWidth, clientHeight, scrollWidth, scrollHeight}) {
        return scrollWidth > clientWidth || scrollHeight > clientHeight;
      }

      function onOverflow(el, fn) {
        new ResizeObserver(
          () => {
            if (isOverflown(el)) {
              fn();
            }
          }
        ).observe(el);
      }

      onOverflow(myElement, () => console.log("overflowing!"));

The ResizeObserver detects any changes to the size of the element. We then check if the element is overflowing after the resize, and execute the desired function if so.

Upvotes: 2

WENDYN
WENDYN

Reputation: 740

The "scrollSize > clientSize" method didn't really work for me so I ended up using getBoundingClientRect.

I also don't know which element in the hiearchy has the overflow: hidden property so I need to iterate over all parent elements and check with getComputedStyle(...).overflow.

function checkNotHiddenByOverflow(element : HTMLElement) : boolean
{
    let parent = element;

    while (parent.parentElement)
    {
        parent = parent.parentElement;

        const rect1 = element.getBoundingClientRect();
        const rect2 = parent.getBoundingClientRect();

        const style = window.getComputedStyle(parent!, null);
        if (style.overflowX === 'hidden')
        {
            if (rect1.right < rect2.left || rect1.left > rect2.right)
                return false;
        }
        if (style.overflowY === 'hidden')
        {
            if (rect1.top > rect2.bottom || rect1.bottom < rect2.top)
                return false;
        }
    }
    return true;
}

Upvotes: 0

Nikhilesh debbarma
Nikhilesh debbarma

Reputation: 11

If someone is looking for an alternative solution to detect the overflow, in reactjs, this might be useful.

export default function App() {
  const [zoom, setZoom] = useState(1);
  const [isOverFlowing, setIsOverFlowing] = useState(false);
  const innerDiv = useRef(null);
  const outerDiv = useRef(null);
  useEffect(() => {
    if (
      innerDiv.current.getBoundingClientRect().height >
        outerDiv.current.getBoundingClientRect().height ||
      innerDiv.current.getBoundingClientRect().width >
        outerDiv.current.getBoundingClientRect().width
    ) {
      setIsOverFlowing(true);
    } else {
      setIsOverFlowing(false);
    }
  }, [zoom, isOverFlowing]);
  return (
    <div className="App">
      <button
        onClick={() => {
          setZoom(zoom + 0.5);
        }}
      >
        Scale +
      </button>
      <button
        onClick={() => {
          setZoom(zoom - 0.5);
        }}
      >
        Scale -
      </button>
      <div
        ref={outerDiv}
        style={{
          width: "100vw",
          height: "100vh",
          backgroundColor: "red",
          overflow: "auto",
          display: "flex",
          justifyContent: "center",
          alignItems: "center"
        }}
      >
        <div
          ref={innerDiv}
          style={{
            width: "100px",
            height: "100px",
            backgroundColor: "green",
            transform: `scale(${zoom})`,
            transformOrigin: `${isOverFlowing ? "0 0" : "center"}`
          }}
        ></div>
      </div>
    </div>
  );
}

Upvotes: 0

Mike Samuel
Mike Samuel

Reputation: 120486

You can check the bounds relative to the offset parent.

// Position of left edge relative to frame left courtesy
// http://www.quirksmode.org/js/findpos.html
function absleft(el) {
  var x = 0;
  for (; el; el = el.offsetParent) {
    x += el.offsetLeft;
  }
  return x;
}

// Position of top edge relative to top of frame.
function abstop(el) {
  var y = 0;
  for (; el; el = el.offsetParent) {
    y += el.offsetTop;
  }
  return y;
}

// True iff el's bounding rectangle includes a non-zero area
// the container's bounding rectangle.
function overflows(el, opt_container) {
  var cont = opt_container || el.offsetParent;
  var left = absleft(el), right = left + el.offsetWidth,
      top = abstop(el), bottom = top + el.offsetHeight;
  var cleft = absleft(cont), cright = cleft + cont.offsetWidth,
      ctop = abstop(cont), cbottom = ctop + cont.offsetHeight;
  return left < cleft || top < ctop
      || right > cright || bottom > cbottom;
}

If you pass this an element it will tell you whether its bounds are entirely inside a container, and will default to the element's offset parent if no explicit container is provided.

Upvotes: 3

MFix
MFix

Reputation: 229

Another issue you should consider is a JS unavailability. Think about progressive enhancement or graceful degradation. I would suggest:

  • adding "more button" by default
  • adding overflow rules by default
  • hiding button and if necessary CSS modifications in JS after comparing element.scrollHeight to element.clientHeight

Upvotes: 3

MVersteeg
MVersteeg

Reputation: 1

Add an event listener for conditional visibility:

document.getElementById('parent').addEventListener('scroll', (e) => {
  const childEl = document.getElementById('child');

  childEl.style.visibility = e.target.scrollTop > 0 ? 'visible' : 'hidden';
});

Upvotes: 0

micnic
micnic

Reputation: 11245

The element may be overflown vertically, horizontally or both. This function will return you a boolean value if the DOM element is overflown:

function isOverflown(element) {
  return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}

function isOverflown(element) {
  return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}

var els = document.getElementsByClassName('demos');
for (var i = 0; i < els.length; i++) {
  var el = els[i];
  el.style.borderColor = (isOverflown(el) ? 'red' : 'green');
  console.log("Element #" + i + " is " + (isOverflown(el) ? '' : 'not ') + "overflown.");
}
.demos {
  white-space: nowrap;
  overflow: hidden;
  width: 120px;
  border: 3px solid black;
}
<div class='demos'>This is some text inside the div which we are testing</div>
<div class='demos'>This is text.</div>

ES6 example:

const isOverflown = ({ clientWidth, clientHeight, scrollWidth, scrollHeight }) => {
    return scrollHeight > clientHeight || scrollWidth > clientWidth;
}

Upvotes: 388

David Clews
David Clews

Reputation: 906

I've extended Element for encapsulation reasons. From micnic answer.

/*
 * isOverflowing
 * 
 * Checks to see if the element has overflowing content
 * 
 * @returns {}
 */
Element.prototype.isOverflowing = function(){
    return this.scrollHeight > this.clientHeight || this.scrollWidth > this.clientWidth;
}

Use it like this

let elementInQuestion = document.getElementById("id_selector");

    if(elementInQuestion.isOverflowing()){
        // do something
    }

Upvotes: 3

RWAM
RWAM

Reputation: 7018

If you want to show only an identifier for more content, then you can do this with pure CSS. I use pure scrolling shadows for this. The trick is the use of background-attachment: local;. Your css looks like this:

.scrollbox {
  overflow: auto;
  width: 200px;
  max-height: 200px;
  margin: 50px auto;

  background:
    /* Shadow covers */
    linear-gradient(white 30%, rgba(255,255,255,0)),
    linear-gradient(rgba(255,255,255,0), white 70%) 0 100%,
    
    /* Shadows */
    radial-gradient(50% 0, farthest-side, rgba(0,0,0,.2), rgba(0,0,0,0)),
    radial-gradient(50% 100%,farthest-side, rgba(0,0,0,.2), rgba(0,0,0,0)) 0 100%;
  background:
    /* Shadow covers */
    linear-gradient(white 30%, rgba(255,255,255,0)),
    linear-gradient(rgba(255,255,255,0), white 70%) 0 100%,
    
    /* Shadows */
    radial-gradient(farthest-side at 50% 0, rgba(0,0,0,.2), rgba(0,0,0,0)),
    radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,.2), rgba(0,0,0,0)) 0 100%;
  background-repeat: no-repeat;
  background-color: white;
  background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
  
  /* Opera doesn't support this in the shorthand */
  background-attachment: local, local, scroll, scroll;
}
<div class="scrollbox">
  <ul>
    <li>Not enough content to scroll</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
  </ul>
</div>


<div class="scrollbox">
  <ul>
    <li>Ah! Scroll below!</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
    <li>9</li>
    <li>10</li>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
    <li>The end!</li>
    <li>No shadow there.</li>
  </ul>
</div>

The code and the example you can find on http://dabblet.com/gist/2462915

And an explanation you can find here: http://lea.verou.me/2012/04/background-attachment-local/.

Upvotes: 160

Antony
Antony

Reputation: 4310

The jquery alternative to the answer is to use the [0] key to access the raw element such as this:

if ($('#elem')[0].scrollHeight > $('#elem')[0].clientHeight){

Upvotes: 0

Vincent Tang
Vincent Tang

Reputation: 4160

I made a multipart codepen demonstrating the above answers ( e.g. using overflow hidden and height) but also how you would expand / collapse overflowed items

Example 1: https://codepen.io/Kagerjay/pen/rraKLB ( Real simple example, no javascript, just to clip overflowed items)

Example 2: https://codepen.io/Kagerjay/pen/LBErJL (Single event handler show more / showless on overflowed items)

Example 3: https://codepen.io/Kagerjay/pen/MBYBoJ (Multi event handler on many show more/ show less on overflowed items)

I have attached example 3 below as well, I use Jade/Pug so it might be a tad verbose. I suggest checking out the codepens I've made its simpler to grasp.

// Overflow boolean checker
function isOverflown(element){
  return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}

// Jquery Toggle Text Plugin
$.fn.toggleText = function(t1, t2){
  if (this.text() == t1) this.text(t2);
  else                   this.text(t1);
  return this;
};

// Toggle Overflow
function toggleOverflow(e){
  e.target.parentElement.classList.toggle("grid-parent--showall");
  $(e.target).toggleText("Show More", "Show LESS");
}

// Where stuff happens
var parents = document.querySelectorAll(".grid-parent");

parents.forEach(parent => {
  if(isOverflown(parent)){
    parent.lastElementChild.classList.add("btn-show");
    parent.lastElementChild.addEventListener('click', toggleOverflow);
  }
})
body {
  background-color: #EEF0ED;
  margin-bottom: 300px;
}

.grid-parent {
  margin: 20px;
  width: 250px;
  background-color: lightgrey;
  display: flex;
  flex-wrap: wrap;
  overflow: hidden;
  max-height: 100px;
  position: relative;
}
.grid-parent--showall {
  max-height: none;
}

.grid-item {
  background-color: blue;
  width: 50px;
  height: 50px;
  box-sizing: border-box;
  border: 1px solid red;
}
.grid-item:nth-of-type(even) {
  background-color: lightblue;
}

.btn-expand {
  display: none;
  z-index: 3;
  position: absolute;
  right: 0px;
  bottom: 0px;
  padding: 3px;
  background-color: red;
  color: white;
}

.btn-show {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section>
  <p>Any grid-parent over 10 child items has a "SHOW MORE" button to expand</p>
  <p>Click "SHOW MORE" to see the results</p>
</section>
<radio></radio>
<div class="wrapper">
  <h3>5 child elements</h3>
  <div class="grid-parent">
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="btn-expand">Show More</div>
  </div>
  <h3>8 child elements</h3>
  <div class="grid-parent">
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="btn-expand">Show More</div>
  </div>
  <h3>10 child elements</h3>
  <div class="grid-parent">
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="btn-expand">Show More</div>
  </div>
  <h3>13 child elements</h3>
  <div class="grid-parent">
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="btn-expand">Show More</div>
  </div>
  <h3>16 child elements</h3>
  <div class="grid-parent">
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="btn-expand">Show More</div>
  </div>
  <h3>19 child elements</h3>
  <div class="grid-parent">
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="grid-item"></div>
    <div class="btn-expand">Show More</div>
  </div>
</div>

Upvotes: 12

Bergi
Bergi

Reputation: 664207

Comparing element.scrollHeight to element.clientHeight should do the task.

Below are the images from MDN explaining Element.scrollHeight and Element.clientHeight.

Scroll Height

Client Height

Upvotes: 30

Dennis Golomazov
Dennis Golomazov

Reputation: 17319

This is the jQuery solution that worked for me. clientWidth etc. didn't work.

function is_overflowing(element, extra_width) {
    return element.position().left + element.width() + extra_width > element.parent().width();
}

If this doesn't work, ensure that elements' parent has the desired width (personally, I had to use parent().parent()). position is relative to the parent. I've also included extra_width because my elements ("tags") contain images which take small time to load, but during the function call they have zero width, spoiling the calculation. To get around that, I use the following calling code:

var extra_width = 0;
$(".tag:visible").each(function() {
    if (!$(this).find("img:visible").width()) {
        // tag image might not be visible at this point,
        // so we add its future width to the overflow calculation
        // the goal is to hide tags that do not fit one line
        extra_width += 28;
    }
    if (is_overflowing($(this), extra_width)) {
        $(this).hide();
    }
});

Hope this helps.

Upvotes: 1

Krunal Modi
Krunal Modi

Reputation: 747

setTimeout(function(){
    isOverflowed(element)           
},500)

function isOverflowed(element){
    return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}

This was worked for me. Thank you.

Upvotes: 0

user1295799
user1295799

Reputation: 21

Here is a fiddle for determining whether an element has been overflowed using a wrapper div with overflow:hidden and JQuery height() to measure the difference between the wrapper and an inner content div.

outers.each(function () {
    var inner_h = $(this).find('.inner').height();
    console.log(inner_h);
    var outer_h = $(this).height();
    console.log(outer_h);
    var overflowed = (inner_h > outer_h) ? true : false;
    console.log("overflowed = ", overflowed);
});

Source: Frameworks & Extensions on jsfiddle.net

Upvotes: 2

Tomas
Tomas

Reputation: 59435

If you are using jQuery, you might try a trick: make outer div with overflow: hiddenand inner div with content. Then use .height() function to check if height of the inner div is greater than the height of the outer div. I'm not sure it will work but give it a try.

Upvotes: 3

Vivek Chandra
Vivek Chandra

Reputation: 4358

use js to check if the child's offsetHeight is more than the parents.. if it is,make the parents overflow scroll/hidden/auto whichever you want and also display:block on the more div..

Upvotes: 1

ayyp
ayyp

Reputation: 6598

Would something like this: http://jsfiddle.net/Skooljester/jWRRA/1/ work? It just checks the height of the content and compares it to the height of the container. If it's greater than you can put in the code to append a "Show more" button.

Update: Added the code to create a "Show More" button at the top of the container.

Upvotes: 5

Related Questions