4

While trying to implement a resizable panel in vanilla JS, trouble started showing up when the panel had scrollbars. I made this test fiddle and to my surprise, the panel became smaller instead of bigger when I added to its width.

I recommend viewing it on JSFiddle here http://jsfiddle.net/y3m9Lo17/ but here's the code on SO as well.

getInnerWidth = function (elem) {
    return parseFloat(window.getComputedStyle(elem).width);
};

getExternalWidth = function (elem) {
    return elem.offsetWidth - getInnerWidth(elem);
};

setTotalWidth = function (elem, width) {
    elem.style.width = (width - getExternalWidth(elem)) + "px";
};

getInnerHeight = function (elem) {
    return parseFloat(window.getComputedStyle(elem).height);
};

getExternalHeight = function (elem) {
    return elem.offsetHeight - getInnerHeight(elem);
};

setTotalHeight = function (elem, height) {
    elem.style.height = (height - getExternalHeight(elem)) + "px";
};


//===========================================================================================

var container = document.getElementById("container");
var content = document.getElementById("content");

minus10css = function () {
    container.style.width = (getInnerWidth(container) - 10) + "px";
};

equal200css = function () {
    container.style.width = "200px";
};

plus10css = function () {
    container.style.width = (getInnerWidth(container) + 10) + "px";
};

minus10 = function () {
    setTotalWidth(container, container.offsetWidth - 10);
};

equal200 = function () {
    setTotalWidth(container, 200);
};

plus10 = function () {
    setTotalWidth(container, container.offsetWidth + 10);
};

cssCalc = function () {
    content.style.height = "calc(100% - 30px)";
};

total300 = function() {
    setTotalHeight(content, 300);
};

css300 = function() {
    content.style.height = "300px";
};
#container {
    background-color: yellow;
    width: 200px;
    height: 300px;
    overflow-y: auto;
}
#content {
    background-color: blue;
    background: -webkit-linear-gradient(green, blue);
    background: -moz-linear-gradient(green, blue);
    background: -o-linear-gradient(green, blue);
    background: linear-gradient(green, blue);
    width: calc(100% - 30px);
    margin: 5px;
    height: calc(100% - 30px);
    color: white;
    padding: 10px;
}
<div id="container">
    <div id="content">
        This element is set to have exactly his parent's width minus its margins
        and paddings thanks to the CSS calc function, so it should fit perfectly
        even when resized.
    </div>
</div>
<br />
<p>CSS width of container:
    <br />
    <button onclick="minus10css()">-=10</button>
    <button onclick="equal200css()">=200</button>
    <button onclick="plus10css()">+=10</button>
</p>
<p>Total width of container:
    <br />
    <button onclick="minus10()">-=10</button>
    <button onclick="equal200()">=200</button>
    <button onclick="plus10()">+=10</button>
</p>
<p><b style="color: red;">Content height</b>
    <br />
    <button onclick="cssCalc()">css calc fit</button>
    <button onclick="total300()">total 300</button>
    <button onclick="css300()">css 300</button>
</p>
<p>Get info<br />
    <button onclick="alert(container.style.width)">css container width</button>
    <button onclick="alert(getInnerWidth(container))">inner container width</button>
</p>

I then made another test and found out that the width returned by getComputedStyle is actually the CSS width minus the width of a scrollbar.

http://jsfiddle.net/q659gzdc/

document.getElementById("cssWidth").innerHTML = "CSS width = " +  document.getElementById("container").style.width;

document.getElementById("computedWidth").innerHTML = "getComputedStyle width = " +  window.getComputedStyle(document.getElementById("container")).width;
#container {
    background-color: yellow;
    overflow-y: auto;
}
#content {
    background-color: blue;
    background: -webkit-linear-gradient(green, blue);
    background: -moz-linear-gradient(green, blue);
    background: -o-linear-gradient(green, blue);
    background: linear-gradient(green, blue);
    width: 100%;
    height: 200%;
    color: white;
}
<div id="container" style="width: 200px; height: 200px;">
    <div id="content">This element is set to have exactly his parent's width minus its margins and paddings thanks to the CSS calc function, so it should fit perfectly even when resized.</div>
</div>

<span id="cssWidth"></span><br />
<span id="computedWidth"></span>

But I can't use the elem.style.width property since the initial value could be a percentage or a calc() css function, and is set through a class (not on the element itself, so the element.style property won't have it). Is there any way to "fix" this Chrome behavior so I get 200px? EDIT: this is also a problem in IE, but Firefox returns the true CSS width.

EDIT: if you don't have chrome to test this, look at this fiddle: http://jsfiddle.net/y8Y32/25/ Chrome's getComputedStyle width is equal to the width of the first paragraph.

Domino
  • 6,314
  • 1
  • 32
  • 58

1 Answers1

1

I understand why browsers return this value: it's because they actually put the scrollbar between the right padding and the right border, and then they shrink the width by that amount so that the offsetWidth (total width) stays the same. Even firefox does that, but it still returns the original width.

I worked out a solution which seems to be browser friendly. I basically do the calculation myself by subtracting the computed padding and borders from the offsetWidth, which gives me the inner width. http://jsfiddle.net/y3m9Lo17/1/

getInnerWidth = function (elem) {
    var style = window.getComputedStyle(elem);
    return elem.offsetWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight) - parseFloat(style.borderLeftWidth) - parseFloat(style.borderRightWidth);
};

getInnerHeight = function (elem) {
    var style = window.getComputedStyle(elem);
    return elem.offsetHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom) - parseFloat(style.borderTopWidth) - parseFloat(style.borderBottomWidth);
};
Domino
  • 6,314
  • 1
  • 32
  • 58