I’m writing a webpage that has a table with rows that slide open and closed. Initially, some rows are closed (display: none
), and I want them to slide open. Setting the height and using overflow: hidden
doesn’t work on table rows, so I’m changing the height of a div inside the table.
This works. The only problem is that I need to know the height of the div before I slide it open, which seems to be impossible. One solution I can think of is to load the page with the rows show, then iterate through them, storing their heights and hiding them. I don’t like this solution because the page would jump around when loading.
Here’s a simple, runnable example of my problem.
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> table, td {border: 1px solid black;} #lower_row {display: none;} #lower_div {overflow: hidden;} </style> <script type="text/javascript"> function toggleLower() { lowerRow = document.getElementById("lower_row"); lowerDiv = document.getElementById("lower_div"); if (getStyle(lowerRow, "display") == "none") { lowerRow.style.display = "table-row"; } else { lowerRow.style.display = "none"; } showHeight(); } function showHeight() { lowerDiv = document.getElementById("lower_div"); document.getElementById("info").innerHTML = getStyle(lowerDiv, "height"); } // Return a style atribute of an element. // J/S Pro Techniques p136 function getStyle(elem, name) { if (elem.style[name]) { return elem.style[name]; } else if (elem.currentStyle) { return elem.currentStyle[name]; } else if (document.defaultView && document.defaultView.getComputedStyle) { name = name.replace(/([A-Z])/g, "-$1"); name = name.toLowerCase(); s = document.defaultView.getComputedStyle(elem, ""); return s && s.getPropertyValue(name); } else { return null; } } </script> </head> <body onload="showHeight()"> <p>The height the lower row is currently <span id="info"></span></p> <table> <tr id="upper_row" onclick="toggleLower()"><td><p>Click me to toggle the next row.</p></td></tr> <tr id="lower_row"><td><div id="lower_div"><p>Peekaboo!</p></div></td></tr> </table> </body> </html>
Edit 1:
One proposed solution is to move the div off the page. I can’t get that to work, and I think it would have the wrong height because its height depends on the width of the table.
I’m working on the solution of using visibility:hidden
, but it has problems. It still takes up a small amount of space, and the reported height is wrong. Here’s an example of that solution:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> table {width: 250px;} table, td {border: 1px solid black;} #lower_row {position: absolute; visibility: hidden} #lower_div {overflow: hidden;} </style> <script type="text/javascript"> function toggleLower() { lowerRow = document.getElementById("lower_row"); lowerDiv = document.getElementById("lower_div"); if (getStyle(lowerRow, "visibility") == "hidden") { lowerRow.style.visibility = "visible"; lowerRow.style.position = "static"; } else { lowerRow.style.visibility = "hidden"; lowerRow.style.position = "absolute"; } showHeight(); } function showHeight() { lowerDiv = document.getElementById("lower_div"); document.getElementById("info").innerHTML = getStyle(lowerDiv, "height"); } // Return a style atribute of an element. // J/S Pro Techniques p136 function getStyle(elem, name) { if (elem.style[name]) { return elem.style[name]; } else if (elem.currentStyle) { return elem.currentStyle[name]; } else if (document.defaultView && document.defaultView.getComputedStyle) { name = name.replace(/([A-Z])/g, "-$1"); name = name.toLowerCase(); s = document.defaultView.getComputedStyle(elem, ""); return s && s.getPropertyValue(name); } else { return null; } } </script> </head> <body onload="showHeight()"> <p>The height the lower row is currently <span id="info"></span></p> <table> <tr id="upper_row" onclick="toggleLower()"><td><p>Click me to toggle the next row.</p></td></tr> <tr id="lower_row"><td><div id="lower_div"><p>This is some long text. This is some long text. This is some long text. This is some long text.</p></div></td></tr> </table> </body> </html>
Edit 2: The Solution
Paul’s answer is the solution to my question: how to find the height of an element that is not displayed. However, it wouldn’t work for my problem. On my site, the height of the div depends on its width, which depends on the td’s width, which depends on the state of the other rows and the width of table, which depends on the width of the page. This means that, even if I pre-compute the height, the value would be wrong as soon as someone expands another row or changes the window size. Also, copying the table and keeping all of these constraints would be near-impossible.
However, I have found a solution. When the user clicks to expand a row, my site would do the following steps in order:
- Set the div.style.height to 1px.
- Set the row.style.display to table-row.
- Store the value of div.scrollHeight.
- Run the scroll animation, stopping at div.scrollHeight.
- After the animation, set div.style.height to auto.
div.scrollHeight gives the height of the div’s contents, including its overflow. It doesn’t work when the div is not displayed, but that’s not a problem for my application. Here’s a sample of the code in action. (Again, I don’t include the code for the scroll animation because it would be too long.)
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> table, td {border: 1px solid black;} #lower_row {display: none;} #lower_div {overflow: hidden;} </style> <script type="text/javascript"> function toggleLower() { var lowerRow = document.getElementById("lower_row"); var lowerDiv = document.getElementById("lower_div"); if (getStyle(lowerRow, "display") == "none") { lowerDiv.style.height = "0px"; lowerRow.style.display = "table-row"; showHeight(); lowerDiv.style.height = "auto"; } else { lowerDiv.style.height = "0px"; showHeight(); lowerRow.style.display = "none"; } } function showHeight() { var lowerDiv = document.getElementById("lower_div"); document.getElementById("info").innerHTML = lowerDiv.scrollHeight; } // Return a style atribute of an element. // J/S Pro Techniques p136 function getStyle(elem, name) { if (elem.style[name]) { return elem.style[name]; } else if (elem.currentStyle) { return elem.currentStyle[name]; } else if (document.defaultView && document.defaultView.getComputedStyle) { name = name.replace(/([A-Z])/g, "-$1"); name = name.toLowerCase(); s = document.defaultView.getComputedStyle(elem, ""); return s && s.getPropertyValue(name); } else { return null; } } </script> </head> <body> <p>The height the lower row is currently <span id="info">...</span></p> <table> <tr id="upper_row" onclick="toggleLower()"><td><p>Click me to toggle the next row.</p></td></tr> <tr id="lower_row"><td><div id="lower_div"><p> This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. This is some long text. </p></div></td></tr> </table> </body> </html>
Advertisement
Answer
You can copy the div with the content and put it to the body with absolute positioning top:-10000; left:-10000;
so it will be outside of visible area, then you can calculate the height and remove clone from the DOM.
UPDATE
Alternatively, in case when you add elements in dynamic way, you can set it to display:block
, position:absolute
, and visibility:hidden
– but you have to make sure that it will not change position of any element on the page. visibility:hidden
– will not show the element, but calculate it’s dimensions (in contrast to display: none
)
UPDATE
In your particular case, your parent have an influence on child’s dimensions, so you need to clone your element into “similar” parent which is outside of visible area. By saying “similar” I mean it should have the same dimensions, but in general – styles and everything what is related to it’s size:
var wrapper = $('<div></div>').appendTo('body').css('display', 'block').css('position', 'absolute').css('top', -10000).css('left', -10000).css('width', $('table').css('width')); var clone = $('#lower_div').clone().appendTo(wrapper); document.getElementById("info").innerHTML = clone.height();
Here is working jsfiddle for you.