Reputation: 54939
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
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
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
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
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
Reputation: 229
Another issue you should consider is a JS unavailability. Think about progressive enhancement or graceful degradation. I would suggest:
Upvotes: 3
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
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
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
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
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
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
Reputation: 664207
Comparing element.scrollHeight
to element.clientHeight
should do the task.
Below are the images from MDN explaining Element.scrollHeight and Element.clientHeight.
Upvotes: 30
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
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
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
Reputation: 59435
If you are using jQuery, you might try a trick: make outer div with overflow: hidden
and 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
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
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