Can I use JavaScript to check (irrespective of scrollbars) if an HTML element has overflowed its content? For example, a long div with small, fixed size, the overflow property set to visible, and no scrollbars on the element.
Normally, you can compare the client[Height|Width] with scroll[Height|Width] in order to detect this... but the values will be the same when overflow is visible. So, a detection routine must account for this:
// Determines if the passed element is overflowing its bounds,
// either vertically or horizontally.
// Will temporarily modify the "overflow" style to detect this
// if necessary.
function checkOverflow(el)
{
var curOverflow = el.style.overflow;
if ( !curOverflow || curOverflow === "visible" )
el.style.overflow = "hidden";
var isOverflowing = el.clientWidth < el.scrollWidth
|| el.clientHeight < el.scrollHeight;
el.style.overflow = curOverflow;
return isOverflowing;
}
Tested in FF3, FF40.0.2, IE6, Chrome 0.2.149.30.
Try comparing element.scrollHeight / element.scrollWidth to element.offsetHeight / element.offsetWidth
http://developer.mozilla.org/en/DOM/element.offsetWidth
http://developer.mozilla.org/en/DOM/element.offsetHeight
http://developer.mozilla.org/en/DOM/element.scrollWidth
http://developer.mozilla.org/en/DOM/element.scrollHeight
Another way is compare the element width with its parent's width:
function checkOverflow(elem) {
const elemWidth = elem.getBoundingClientRect().width
const parentWidth = elem.parentElement.getBoundingClientRect().width
return elemWidth > parentWidth
}
I don't think this answer is perfect. Sometimes the scrollWidth/clientWidth/offsetWidth are the same even though the text is overflow.
This works well in Chrome, but not in IE and Firefox.
At last, I tried this answer: HTML text-overflow ellipsis detection
It's perfect and works well anywhere. So I choose this, maybe you can try, you won't disappoint.
I didn't like any of these, so I wrote this one. Works great!
function isOverflowY(element) {
return element.scrollHeight != Math.max(element.offsetHeight, element.clientHeight)
}
With jQuery you could do:
if ( $(".inner-element").prop('scrollHeight') > $(".inner-element").height() ) {
console.log("element is overflowing");
} else {
console.log("element is not overflowing");
}
Change to .prop('scrollWidth') and .width() if needed.
This is a javascript solution (with Mootools) that will reduce the font size to fit the bounds of elHeader.
while (elHeader.clientWidth < elHeader.scrollWidth || elHeader.clientHeight < elHeader.scrollHeight) {
var f = parseInt(elHeader.getStyle('font-size'), 10);
f--;
elHeader.setStyle('font-size', f + 'px');
}
The CSS of elHeader:
width:100%;
font-size:40px;
line-height:36px;
font-family:Arial;
text-align:center;
max-height:36px;
overflow:hidden;
Note the wrapper of elHeader sets the width of elHeader.
Related
This question already has answers here:
Check if element is visible in DOM
(27 answers)
Closed 6 years ago.
In JavaScript, how would you check if an element is actually visible?
I don't just mean checking the visibility and display attributes. I mean, checking that the element is not
visibility: hidden or display: none
underneath another element
scrolled off the edge of the screen
For technical reasons I can't include any scripts. I can however use Prototype as it is on the page already.
For the point 2.
I see that no one has suggested to use document.elementFromPoint(x,y), to me it is the fastest way to test if an element is nested or hidden by another. You can pass the offsets of the targetted element to the function.
Here's PPK test page on elementFromPoint.
From MDN's documentation:
The elementFromPoint() method—available on both the Document and ShadowRoot objects—returns the topmost Element at the specified coordinates (relative to the viewport).
I don't know how much of this is supported in older or not-so-modern browsers, but I'm using something like this (without the neeed for any libraries):
function visible(element) {
if (element.offsetWidth === 0 || element.offsetHeight === 0) return false;
var height = document.documentElement.clientHeight,
rects = element.getClientRects(),
on_top = function(r) {
var x = (r.left + r.right)/2, y = (r.top + r.bottom)/2;
return document.elementFromPoint(x, y) === element;
};
for (var i = 0, l = rects.length; i < l; i++) {
var r = rects[i],
in_viewport = r.top > 0 ? r.top <= height : (r.bottom > 0 && r.bottom <= height);
if (in_viewport && on_top(r)) return true;
}
return false;
}
It checks that the element has an area > 0 and then it checks if any part of the element is within the viewport and that it is not hidden "under" another element (actually I only check on a single point in the center of the element, so it's not 100% assured -- but you could just modify the script to itterate over all the points of the element, if you really need to...).
Update
Modified on_top function that check every pixel:
on_top = function(r) {
for (var x = Math.floor(r.left), x_max = Math.ceil(r.right); x <= x_max; x++)
for (var y = Math.floor(r.top), y_max = Math.ceil(r.bottom); y <= y_max; y++) {
if (document.elementFromPoint(x, y) === element) return true;
}
return false;
};
Don't know about the performance :)
As jkl pointed out, checking the element's visibility or display is not enough. You do have to check its ancestors. Selenium does this when it verifies visibility on an element.
Check out the method Selenium.prototype.isVisible in the selenium-api.js file.
http://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails/selenium-core/scripts/selenium-api.js
Interesting question.
This would be my approach.
At first check that element.style.visibility !== 'hidden' && element.style.display !== 'none'
Then test with document.elementFromPoint(element.offsetLeft, element.offsetTop) if the returned element is the element I expect, this is tricky to detect if an element is overlapping another completely.
Finally test if offsetTop and offsetLeft are located in the viewport taking scroll offsets into account.
Hope it helps.
This is what I have so far. It covers both 1 and 3. I'm however still struggling with 2 since I'm not that familiar with Prototype (I'm more a jQuery type of guy).
function isVisible( elem ) {
var $elem = $(elem);
// First check if elem is hidden through css as this is not very costly:
if ($elem.getStyle('display') == 'none' || $elem.getStyle('visibility') == 'hidden' ) {
//elem is set through CSS stylesheet or inline to invisible
return false;
}
//Now check for the elem being outside of the viewport
var $elemOffset = $elem.viewportOffset();
if ($elemOffset.left < 0 || $elemOffset.top < 0) {
//elem is left of or above viewport
return false;
}
var vp = document.viewport.getDimensions();
if ($elemOffset.left > vp.width || $elemOffset.top > vp.height) {
//elem is below or right of vp
return false;
}
//Now check for elements positioned on top:
//TODO: Build check for this using Prototype...
//Neither of these was true, so the elem was visible:
return true;
}
/**
* Checks display and visibility of elements and it's parents
* #param DomElement el
* #param boolean isDeep Watch parents? Default is true
* #return {Boolean}
*
* #author Oleksandr Knyga <oleksandrknyga#gmail.com>
*/
function isVisible(el, isDeep) {
var elIsVisible = true;
if("undefined" === typeof isDeep) {
isDeep = true;
}
elIsVisible = elIsVisible && el.offsetWidth > 0 && el.offsetHeight > 0;
if(isDeep && elIsVisible) {
while('BODY' != el.tagName && elIsVisible) {
elIsVisible = elIsVisible && 'hidden' != window.getComputedStyle(el).visibility;
el = el.parentElement;
}
}
return elIsVisible;
}
You can use the clientHeight or clientWidth properties
function isViewable(element){
return (element.clientHeight > 0);
}
Prototype's Element library is one of the most powerful query libraries in terms of the methods. I recommend you to check out the API.
A few hints:
Checking visibility can be a pain, but you can use the Element.getStyle() method and Element.visible() methods combined into a custom function. With getStyle() you can check the actual computed style.
I don't know exactly what you mean by "underneath" :) If you meant by it has a specific ancestor, for example, a wrapper div, you can use Element.up(cssRule):
var child = $("myparagraph");
if(!child.up("mywrapper")){
// I lost my mom!
}
else {
// I found my mom!
}
If you want to check the siblings of the child element you can do that too:
var child = $("myparagraph");
if(!child.previous("mywrapper")){
// I lost my bro!
}
else {
// I found my bro!
}
Again, Element lib can help you if I understand correctly what you mean :) You can check the actual dimensions of the viewport and the offset of your element so you can calculate if your element is "off screen".
Good luck!
I pasted a test case for prototypejs at http://gist.github.com/117125. It seems in your case we simply cannot trust in getStyle() at all. For maximizing the reliability of the isMyElementReallyVisible function you should combine the following:
Checking the computed style (dojo has a nice implementation that you can borrow)
Checking the viewportoffset (prototype native method)
Checking the z-index for the "beneath" problem (under Internet Explorer it may be buggy)
One way to do it is:
isVisible(elm) {
while(elm.tagName != 'BODY') {
if(!$(elm).visible()) return false;
elm = elm.parentNode;
}
return true;
}
Credits: https://github.com/atetlaw/Really-Easy-Field-Validation/blob/master/validation.js#L178
Try element.getBoundingClientRect().
It will return an object with properties
bottom
top
right
left
width -- browser dependent
height -- browser dependent
Check that the width and height of the element's BoundingClientRect are not zero which is the value of hidden or non-visible elements. If the values are greater than zero the element should be visible in the body. Then check if the bottom property is less than screen.height which would imply that the element is withing the viewport. (Technically you would also have to account for the top of the browser window including the searchbar, buttons, etc.)
Catch mouse-drag and viewport events (onmouseup, onresize, onscroll).
When a drag ends do a comparison of the dragged item boundary with all "elements of interest" (ie, elements with class "dont_hide" or an array of ids). Do the same with window.onscroll and window.onresize. Mark any elements hidden with a special attribute or classname or simply perform whatever action you want then and there.
The hidden tests are pretty easy. For "totally hidden" you want to know if ALL corners are either inside the dragged-item boundary or outside the viewport. For partially hidden you're looking for a single corner matching the same test.
I don't think checking the element's own visibility and display properties is good enough for requirement #1, even if you use currentStyle/getComputedStyle. You also have to check the element's ancestors. If an ancestor is hidden, so is the element.
Check elements' offsetHeight property. If it is more than 0, it is visible. Note: this approach doesn't cover a situation when visibility:hidden style is set. But that style is something weird anyways.
Here is a sample script and test case. Covers positioned elements, visibilty: hidden, display: none. Didn't test z-index, assume it works.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<style type="text/css">
div {
width: 200px;
border: 1px solid red;
}
p {
border: 2px solid green;
}
.r {
border: 1px solid #BB3333;
background: #EE9999;
position: relative;
top: -50px;
height: 2em;
}
.of {
overflow: hidden;
height: 2em;
word-wrap: none;
}
.of p {
width: 100%;
}
.of pre {
display: inline;
}
.iv {
visibility: hidden;
}
.dn {
display: none;
}
</style>
<script src="http://www.prototypejs.org/assets/2008/9/29/prototype-1.6.0.3.js"></script>
<script>
function isVisible(elem){
if (Element.getStyle(elem, 'visibility') == 'hidden' || Element.getStyle(elem, 'display') == 'none') {
return false;
}
var topx, topy, botx, boty;
var offset = Element.positionedOffset(elem);
topx = offset.left;
topy = offset.top;
botx = Element.getWidth(elem) + topx;
boty = Element.getHeight(elem) + topy;
var v = false;
for (var x = topx; x <= botx; x++) {
for(var y = topy; y <= boty; y++) {
if (document.elementFromPoint(x,y) == elem) {
// item is visible
v = true;
break;
}
}
if (v == true) {
break;
}
}
return v;
}
window.onload=function() {
var es = Element.descendants('body');
for (var i = 0; i < es.length; i++ ) {
if (!isVisible(es[i])) {
alert(es[i].tagName);
}
}
}
</script>
</head>
<body id='body'>
<div class="s"><p>This is text</p><p>More text</p></div>
<div class="r">This is relative</div>
<div class="of"><p>This is too wide...</p><pre>hidden</pre>
<div class="iv">This is invisible</div>
<div class="dn">This is display none</div>
</body>
</html>
Here is a part of the response that tells you if an element is in the viewport.
You may need to check if there is nothing on top of it using elementFromPoint, but it's a bit longer.
function isInViewport(element) {
var rect = element.getBoundingClientRect();
var windowHeight = window.innerHeight || document.documentElement.clientHeight;
var windowWidth = window.innerWidth || document.documentElement.clientWidth;
return rect.bottom > 0 && rect.top < windowHeight && rect.right > 0 && rect.left < windowWidth;
}
I've started with the below which works perfect, but I only need this to run and execute when my nav element .mainNav is scrolled to within a certain point within browser height.
#media screen and (max-height: 660px) {
.mainNav {
margin-top:-130px !important;
}
}
So detecting vertical browser height WITH scroll position within then chain .css with jQuery to. (margin-top:-100px)
Basically how to combine above parameter with below parameter. Below detects scroll position...
var $document = $(document),
$element = $('#some-element'),
className = 'hasScrolled';
$document.scroll(function() {
if ($document.scrollTop() >= 50) {
// user scrolled 50 pixels or more;
// do stuff
$element.addClass(className);
} else {
$element.removeClass(className);
}
});
You could do this by using the clientBoundingRect of the element. For example (untested, just theory);
var mainNav = document.getElementsByClassName('.mainNav')[0];
window.onscroll(function(e){
if(mainNav.clientBoundingRect().top <= 100 && window.height <= 660){
mainNav.style.marginTop = "-130px";
// or you could add a class here, as per the suggestion above, such as
// mainNav.setAttribute('class', 'mainNav locked');
}else{
mainNav.style.marginTop = "0";
// and remove it here
// mainNav.setAttribute('class', 'mainNav');
}
})
There is no visbility pseudo-selector in CSS, so you're looking at implementing this using JS handing of the visibilitychange event on your element, and then toggling the right (sequence of) class(es) through the element.classList interface, probably with transition rules in the CSS itself for the properties you want to be dynamic.
I can't for some reason make a smooth div, I'm trying to make it expand and contract when I click a button, but it doesn't work.
Javascript:
function expandContract(id) {
var object = document.getElementById(id);
if (object.style.height != "0px") {
object.style.height = "0px";
} else {
object.style.height = object.scrollHeight;
}
}
HTML:
<div id='test' style='overflow:hidden;'>
Test pest fest.
</div>
<button onClick='expandContract("test");'>Expand/Contract</button>
jsFiddle
I've also tried setting it do max-height, but still I can't get it to expand again. How would I do this without any javascript plugins?
You're missing +"px". You are required to set a unit (em, px, %, etc) when using style.height. Because scrollHeight gives you just a numeric value, you must append the units which are px in this case.
function expandContract(id) {
var object = document.getElementById(id);
if (object.style.height != "0px") {
object.style.height = "0px";
} else {
object.style.height = object.scrollHeight+"px";
}
}
jsFiddle
I got it to work in Chrome, as is.
IE when I changed the 'overflow' to 'scroll'.
how can i build fixed menu like gmail menu. i have tried css, but the div stays in middle, it doesnt come up like the gmail menu does on scroll.
open in large image
i have tried using css property, following is some example code (not real code):
.menu {
position:fixed;
height: 36px;
background-color:#fff;
}
You need to use javascript to check the scrollTop and set the position of your menu to fixed if if the scrollTop is more than the height of your header.
function getScrollTop() {
if(typeof pageYOffset!= 'undefined') {
//most browsers
return pageYOffset;
}
else {
var b = document.body; //IE 'quirks'
var d = document.documentElement; //IE with doctype
d = (d.clientHeight) ? d : b;
return d.scrollTop;
}
}
function onScroll() {
var menu = document.getElementById('divMyMenu');
var headerAndNavHeight = document.getElementById('divHeader').clientHeight
+ document.getElementById('tsMain').clientHeight;
if (getScrollTop() < headerAndNavHeight) {
menu.style.top = headerAndNavHeight + 'px';
menu.style.position = 'absolute';
}
else {
menu.style.top = '0px';
menu.style.position = 'fixed';
}
}
A good and easy to use jQuery Plugin for this is Waypoints
Here you can see a working example:
http://imakewebthings.github.com/jquery-waypoints/sticky-elements/
Position fixed alone is not enough to achieve this effect. Also, position:fixed does not work in IE7 or below, so you'll probably want to have fallback.
You need to use javascript (jQuery makes it easy) to dynamically change the position of the element based upon how far scrolled down the page you are.
Look into .scrollTop()
http://api.jquery.com/scrollTop/
var scrollTop = $(window).scrollTop();
May be this is what you are looking for
http://blog.geotitles.com/2011/10/creating-top-fixed-menu-bar-with-css3-buttons-found-in-gmail/
Here is a very simple trick to implement your requirement explained with example and a link to download.
http://itswadesh.wordpress.com/2012/02/24/google-like-top-bar-with-drop-down-menu-using-html-css-and-jquery/
I have a web page that uses a scrolling div to display table information. When the window is resized (and also on page load), the display is centered and the div's scrollbar positioned to the right of the page by setting its width. For some reason, the behaviour is different under firefox than IE. IE positions/sizes the div as expected, but firefox seems to make it too wide, such that the scrollbar begins to disappear when the window client width reaches about 800px. I'm using the following methods to set the position and size:
function getWindowWidth() {
var windowWidth = 0;
if (typeof(window.innerWidth) == 'number') {
windowWidth=window.innerWidth;
}
else {
if (document.documentElement && document.documentElement.clientWidth) {
windowWidth=document.documentElement.clientWidth ;
}
else {
if (document.body && document.body.clientWidth) {
windowWidth=document.body.clientWidth;
}
}
}
return windowWidth;
}
function findLPos(obj) {
var curleft = 0;
if (obj.offsetParent) {
curleft = obj.offsetLeft
while (obj = obj.offsetParent) {
curleft += obj.offsetLeft
}
}
return curleft;
}
var bdydiv;
var coldiv;
document.body.style.overflow="hidden";
window.onload=resizeDivs;
window.onresize=resizeDivs;
function resizeDivs(){
bdydiv=document.getElementById('bdydiv');
coldiv=document.getElementById('coldiv');
var winWdth=getWindowWidth();
var rghtMarg = 0;
var colHdrTbl=document.getElementById('colHdrTbl');
rghtMarg = parseInt((winWdth - 766) / 2) - 8;
rghtMarg = (rghtMarg > 0 ? rghtMarg : 0);
coldiv.style.paddingLeft = rghtMarg + "px";
bdydiv.style.paddingLeft = rghtMarg + "px";
var bdydivLft=findLPos(bdydiv);
if ((winWdth - bdydivLft) >= 1){
bdydiv.style.width = winWdth - bdydivLft;
coldiv.style.width = bdydiv.style.width;
}
syncScroll();
}
function syncScroll(){
if(coldiv.scrollLeft>=0){
coldiv.scrollLeft=bdydiv.scrollLeft;
}
}
Note that I've cut out other code which sets height, and other non-relevant parts. The full page can be seen here. If you go to the link in both IE and firefox, resize width until "800" is displayed in the green box top-right, and resize height until the scrollbar at the right is enabled, you can see the problem. If you then resize the IE width, the scrollbar stays, but if you resize the firefox width wider, the scrollbar begins to disappear. I'm at a loss as to why this is happening....
Note that AFAIK, getWindowWidth() should be cross-browser-compatible, but I'm not so sure about findLPos().... perhaps there's an extra object in Firefox's DOM or something, which is changing the result??
You are dealing with "one of the best-known software bugs in a popular implementation of Cascading Style Sheets (CSS)" according to Wikipedia. I recommend the Element dimensions and CSS Object Model View pages on Quirksmode.org.
Also: I think you'll find that Safari and Opera behave like Firefox in most circumstances. A more compatible approach to working around these problems is testing for, and making exceptions for, MSIE instead of the other way around.
Ok, I found the problem. Seems to be that firefox does not include the style.paddingLeft value in its style.width setting, whereas IE does, thus the div was ending up exactly style.paddingLeft too wide. That is, if for example style.paddingLeft is 8, IE's style.width value would be 8 more than FireFox's - and thus the inverse when setting the value, for FireFox I needed to subtract the style.paddingLeft value
Modified code with:
if (__isFireFox){
bdydiv.style.width = winWdth - bdydivLft - rghtMarg;
} else {
bdydiv.style.width = winWdth - bdydivLft;
}
As long as you don't include a valid doctype, you can't expect consistent results, due to Quirks Mode. Go add one (HTML 4.01 Transitional is fine), then let us know if it still occurs.
Also see http://en.wikipedia.org/wiki/Quirks_mode.
In your getWindowWidth() function, whenever you grab the width of something, instead of this:
windowWidth = document.documentElement.clientWidth;
try this
windowWidth = Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth);
A detail to help optimize some of your code:
function getPos(elm) {//jumper
for(var zx=zy=0;elm!=null;zx+=elm.offsetLeft,zy+=elm.offsetTop,elm=elm.offsetParent);
return {x:zx,y:zy}
}
(jumper is a user who posted this code in Eksperten.dk)