Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find the height of an element that is not displayed

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:

  1. Set the div.style.height to 1px.
  2. Set the row.style.display to table-row.
  3. Store the value of div.scrollHeight.
  4. Run the scroll animation, stopping at div.scrollHeight.
  5. 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>
like image 313
dln385 Avatar asked Nov 18 '11 18:11

dln385


2 Answers

You can do this by showing the element, capturing the height, and then re-hiding the element all before the browser repaints the screen. So, the user will never notice anything happening. I do this for a packaged used to slide open & closed elements with dynamic heights:

https://github.com/alexmacarthur/slide-element/blob/master/src/index.ts

In super simple code:

<div id="element" style="display: none;">hello</div>

<script>
   const element = document.getElementById('element');
   
   element.style.display = "";
   const elementHeight = element.clientHeight;
   element.style.display = "none";

   console.log(elementHeight); // whatever the rendered height is.
</script>
like image 129
Alex MacArthur Avatar answered Sep 24 '22 01:09

Alex MacArthur


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.

like image 33
Pavel Podlipensky Avatar answered Sep 24 '22 01:09

Pavel Podlipensky