Calculating the maximum / minimum height of a div element

Problem: Given a fixed-height DIV element that contains an unknown number of children, the size of which depends on its height, calculate the maximum / minimum height that the DIV can be resized to without violating any maximum / minimum values ​​of its child element .

Example Find the maximum / minimum height DIV A

Answer

Minimum: 150px

Maximum: 275px

* { box-sizing: border-box; } .border { border-style: solid; border-width: 1px 1px 1px 1px; } .A { height: 200px; width: 200px; } .B { float: left; width: 50%; height: 75%; min-height: 125px; max-height: 225px; background: yellow; } .C { float: left; width: 50%; height: 75%; min-height: 100px; max-height: 250px; background: green; } .D{ float: left; width: 100%; height: 25%; min-height: 25px; max-height: 50px; background: blue; } 
 <div class="A border"> <div class="B border"> B </div> <div class="C border"> C </div> <div class="D border"> D </div> </div> 

Additional Information: Currently, I tried to use an algorithm that traverses the DIV DOM tree and creates an object graph representing the spatial positioning of elements using the element offset. Below is a rudimentary algorithm that explores the spatial relationship of elements, allowing you to divide 10px between edges by a "touch".

jQuery and other libraries are allowed as long as they are open.

 var _isContentRoot = function(a,b){ var aRect = a.innerRect; var bRect = b.outerRect; //Check if child element is a root node return Math.abs(aRect.top - bRect.top) <= 10; } var _isLayoutSibling = function(a,b){ var aRect = a.outerRect; var bRect = b.outerRect; // If element X has a boundary that intersects element Y, and // element X is located above element Y, element Y is a child node of // element X if(Math.abs(aRect.bottom - bRect.top) <= 10) { if (aRect.left <= bRect.left && aRect.right >= bRect.left || aRect.left <= bRect.right && aRect.right >= bRect.right || aRect.left >= bRect.left && aRect.right <= bRect.right || aRect.left <= bRect.left && aRect.right >= bRect.right) { return true; } } return false; } 

Edit: Fixed CSS error. Here is the updated fiddle http://jsfiddle.net/zqnscmo2/

Edit 2: try to think about this problem with a graph in the CSS / HTML problem space. Imagine CSS and HTML used to describe a graph where each DIV is a vertex. There is a line between two peaks

1.) if the HTML element bounding rectA.top β‰ˆ rectB.top OR 2.) there is an edge if the bounding rectangle β‰ˆ rectB.top

Each vertex has two exceptional sets of edges, the set A contains all edges that satisfy criterion 1. The set B contains all edges that meet criterion 2. Therefore, you can cross the graph and find the minimum and maximum path, and this should be the PARENT DIV max / min height.

This is my proposed algorithm for determining the maximum / minimum height of the internal content. I am very open to less complicated decisions.

+7
javascript jquery html css
source share
3 answers

That's what I think:

  • Find the closest ancestor with apparent height
  • Find all the ancestors with a percentage height and calculate the height of the nearest of these ancestors to find the available height. Lets name this ancestor NAR and NARH height.
  • Find the distance your element is from the top of its parent (using getBoundingClientRect). Call him DT
  • Subtract the upper bound of the NAR from the DT. Call it A.
  • Maximum height must be NARH-A

Something similar could be done for a minimum.

UPDATE: Ohhh, I implemented this idea and it works! It has a lot of crap that it takes into account, including margins, borders, indents, scrollbars (even with custom widths), percentage widths, maximum height / width, and sister nodes. Check this code:

 exports.findMaxHeight = function(domNode) { return findMaxDimension(domNode,'height') } exports.findMaxWidth = function(domNode) { return findMaxDimension(domNode,'width') } // finds the maximum height/width (in px) that the passed domNode can take without going outside the boundaries of its parent // dimension - either 'height' or 'width' function findMaxDimension(domNode, dimension) { if(dimension === 'height') { var inner = 'Top' var outer = 'Bottom' var axis = 'Y' var otherAxis = 'X' var otherDimension = 'width' } else { var inner = 'Left' var outer = 'Right' var axis = 'X' var otherAxis = 'Y' var otherDimension = 'height' } var maxDimension = 'max'+dimension[0].toUpperCase()+dimension.slice(1) var innerBorderWidth = 'border'+inner+'Width' var outerBorderWidth = 'border'+outer+'Width' var innerPaddingWidth = 'padding'+inner var outerPaddingWidth = 'padding'+outer var innerMarginWidth = 'margin'+inner var outerMarginWidth = 'margin'+outer var overflowDimension = 'overflow'+axis var propertiesToFetch = [ dimension,maxDimension, overflowDimension, innerBorderWidth,outerBorderWidth, innerPaddingWidth,outerPaddingWidth, innerMarginWidth, outerMarginWidth ] // find nearest ancestor with an explicit height/width and capture all the ancestors in between // find the ancestors with heights/widths relative to that one var ancestry = [], ancestorBottomBorder=0 for(var x=domNode.parentNode; x!=null && x!==document.body.parentNode; x=x.parentNode) { var styles = getFinalStyle(x,propertiesToFetch) var h = styles[dimension] if(h.indexOf('%') === -1 && h.match(new RegExp('\\d')) !== null) { // not a percentage and some kind of length var nearestAncestorWithExplicitDimension = x var explicitLength = h ancestorBottomBorder = parseInt(styles[outerBorderWidth]) + parseInt(styles[outerPaddingWidth]) if(hasScrollBars(x, axis, styles)) ancestorBottomBorder+= getScrollbarLength(x,dimension) break; } else { ancestry.push({node:x, styles:styles}) } } if(!nearestAncestorWithExplicitDimension) return undefined // no maximum ancestry.reverse() var maxAvailableDimension = lengthToPixels(explicitLength) var nodeToFindDistanceFrom = nearestAncestorWithExplicitDimension ancestry.forEach(function(ancestorInfo) { var styles = ancestorInfo.styles var newDimension = lengthToPixels(styles[dimension],maxAvailableDimension) var possibleNewDimension = lengthToPixels(styles[maxDimension], maxAvailableDimension) var moreBottomBorder = parseInt(styles[outerBorderWidth]) + parseInt(styles[outerPaddingWidth]) + parseInt(styles[outerMarginWidth]) if(hasScrollBars(ancestorInfo.node, otherAxis, styles)) moreBottomBorder+= getScrollbarLength(ancestorInfo.node,otherDimension) if(possibleNewDimension !== undefined && ( newDimension !== undefined && possibleNewDimension < newDimension || possibleNewDimension < maxAvailableDimension ) ) { maxAvailableDimension = possibleNewDimension nodeToFindDistanceFrom = ancestorInfo.node // ancestorBottomBorder = moreBottomBorder } else if(newDimension !== undefined) { maxAvailableDimension = newDimension nodeToFindDistanceFrom = ancestorInfo.node // ancestorBottomBorder = moreBottomBorder } else { } ancestorBottomBorder += moreBottomBorder }) // find the distance from the top var computedStyle = getComputedStyle(domNode) var verticalBorderWidth = parseInt(computedStyle[outerBorderWidth]) + parseInt(computedStyle[innerBorderWidth]) + parseInt(computedStyle[outerPaddingWidth]) + parseInt(computedStyle[innerPaddingWidth]) + parseInt(computedStyle[outerMarginWidth]) + parseInt(computedStyle[innerMarginWidth]) var distanceFromSide = domNode.getBoundingClientRect()[inner.toLowerCase()] - nodeToFindDistanceFrom.getBoundingClientRect()[inner.toLowerCase()] return maxAvailableDimension-ancestorBottomBorder-verticalBorderWidth-distanceFromSide } // gets the pixel length of a value defined in a real absolute or relative measurement (eg mm) function lengthToPixels(length, parentLength) { if(length.indexOf('calc') === 0) { var innerds = length.slice('calc('.length, -1) return caculateCalc(innerds, parentLength) } else { return basicLengthToPixels(length, parentLength) } } // ignores the existences of 'calc' function basicLengthToPixels(length, parentLength) { var lengthParts = length.match(/(-?[0-9]+)(.*)/) if(lengthParts != null) { var number = parseInt(lengthParts[1]) var metric = lengthParts[2] if(metric === '%') { return parentLength*number/100 } else { if(lengthToPixels.cache === undefined) lengthToPixels.cache = {}//{px:1} var conversion = lengthToPixels.cache[metric] if(conversion === undefined) { var tester = document.createElement('div') tester.style.width = 1+metric tester.style.visibility = 'hidden' tester.style.display = 'absolute' document.body.appendChild(tester) conversion = lengthToPixels.cache[metric] = tester.offsetWidth document.body.removeChild(tester) } return conversion*number } } } // https://developer.mozilla.org/en-US/docs/Web/CSS/number var number = '(?:\\+|-)?'+ // negative or positive operator '\\d*'+ // integer part '(?:\\.\\d*)?'+ // fraction part '(?:e(?:\\+|-)?\\d*)?' // scientific notation // https://developer.mozilla.org/en-US/docs/Web/CSS/calc var calcValue = '(?:'+ '('+number+')'+ // length number '([A-Za-z]+|%)?'+ // optional suffix (% or px/mm/etc) '|'+ '(\\(.*\\))'+ // more stuff in parens ')' var calcSequence = calcValue+ '((\\s*'+ '(\\*|/|\\+|-)'+ '\\s*'+calcValue+ ')*)' var calcSequenceItem = '\\s*'+ '(\\*|/|\\+|-)'+ '\\s*'+calcValue var caculateCalc = function(calcExpression, parentLength) { var info = calcExpression.match(new RegExp('^'+calcValue)) var number = info[1] var suffix = info[2] var calcVal = info[3] var curSum = 0, curProduct = getCalcNumber(number, suffix, calcVal, parentLength), curSumOp = '+' var curCalcExpression = calcExpression.slice(info[0].length) while(curCalcExpression.length > 0) { info = curCalcExpression.match(new RegExp(calcSequenceItem)) var op = info[1] number = info[2] suffix = info[3] calcVal = info[4] var length = getCalcNumber(number,suffix,calcVal, parentLength) if(op in {'*':1,'/':1}) { curProduct = calcSimpleExpr(curProduct,op,length) } else if(op === '+' || op === '-') { curSum = calcSimpleExpr(curSum,curSumOp,curProduct) curSumOp = op curProduct = length } curCalcExpression = curCalcExpression.slice(info[0].length) } curSum = calcSimpleExpr(curSum,curSumOp,curProduct) return curSum } function calcSimpleExpr(operand1, op, operand2) { if(op === '*') { return operand1 * operand2 } else if(op === '/') { return operand1 / operand2 } else if(op === '+') { return operand1 + operand2 } else if(op === '-') { return operand1 - operand2 } else { throw new Error("bad") } } function getCalcNumber(number, suffix, calcVal, parentLength) { if(calcVal) { return caculateCalc(calcVal, parentLength) } else if(suffix) { return basicLengthToPixels(number+suffix, parentLength) } else { return number } } // gets the style property as rendered via any means (style sheets, inline, etc) but does *not* compute values // domNode - the node to get properties for // properties - Can be a single property to fetch or an array of properties to fetch function getFinalStyle(domNode, properties) { if(!(properties instanceof Array)) properties = [properties] var parent = domNode.parentNode if(parent) { var originalDisplay = parent.style.display parent.style.display = 'none' } var computedStyles = getComputedStyle(domNode) var result = {} properties.forEach(function(prop) { result[prop] = computedStyles[prop] }) if(parent) { parent.style.display = originalDisplay } return result } // from lostsource http://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript // dimension - either 'width' or 'height' function getScrollbarLength(domNode, dimension) { if(dimension === 'width') { var offsetDimension = 'offsetWidth' } else { var offsetDimension = 'offsetHeight' } var outer = document.createElement(domNode.nodeName) outer.className = domNode.className outer.style.cssText = domNode.style.cssText outer.style.visibility = "hidden" outer.style.width = "100px" outer.style.height = "100px" outer.style.top = "0" outer.style.left = "0" outer.style.msOverflowStyle = "scrollbar" // needed for WinJS apps domNode.parentNode.appendChild(outer) var lengthNoScroll = outer[offsetDimension] // force scrollbars with both css and a wider inner div var inner1 = document.createElement("div") inner1.style[dimension] = "120%" // without this extra inner div, some browsers may decide not to add scoll bars outer.appendChild(inner1) outer.style.overflow = "scroll" var inner2 = document.createElement("div") inner2.style[dimension] = "100%" outer.appendChild(inner2) // this must be added after scroll bars are added or browsers are stupid and don't properly resize the object (or maybe they do after a return to the scheduler?) var lengthWithScroll = inner2[offsetDimension] domNode.parentNode.removeChild(outer) return lengthNoScroll - lengthWithScroll } // dimension - Either 'y' or 'x' // computedStyles - (Optional) Pass in the domNodes computed styles if you already have it (since I hear its somewhat expensive) function hasScrollBars(domNode, dimension, computedStyles) { dimension = dimension.toUpperCase() if(dimension === 'Y') { var length = 'Height' } else { var length = 'Width' } var scrollLength = 'scroll'+length var clientLength = 'client'+length var overflowDimension = 'overflow'+dimension var hasVScroll = domNode[scrollLength] > domNode[clientLength] // Check the overflow and overflowY properties for "auto" and "visible" values var cStyle = computedStyles || getComputedStyle(domNode) return hasVScroll && (cStyle[overflowDimension] == "visible" || cStyle[overflowDimension] == "auto" ) || cStyle[overflowDimension] == "scroll" } 

I probably put this in the npm / github module, because it seems like it should be accessible naively, but it is not, and it takes a lot of effort to work properly.

+1
source share

If I understood your question correctly, will it work?

 // - I use two support functions that can probably be found in other JSes frameworks, and they're down below. function calculateMySizes(someElement) { var childDiv = findChild(someElement, "DIV"); var totalWidth = 0; var totalHeight = 0; var maxWidth = 0; var maxHeight = 0; do { if(childDiv.offsetLeft > maxWidth) { maxWidth = childDiv.offsetLeft; totalWidth += childDiv.offsetLeft; } if(childDiv.offsetTop > maxHeight) { maxHeight = childDiv.offsetTop; totalHeight += childDiv.offsetTop; } } while (childDiv = nextElement(childDiv)); alert("object current width is: " + totalWidth + " and it child largest width is: " + maxWidth); alert("object current height is: " + totalHeight + " and it child largest height is: " + maxHeight); } // - Returns the next Element of object function nextElement(object) { var nextObject = object; while (nextObject = nextObject.nextSibling) { if (nextObject.nodeType == 1) { return nextObject; } } return nextObject; } // - Returns the first child of elementName found function findChild(object, elementName) { for (var i = 0; i < object.childNodes.length; i++) { if (object.childNodes[i].nodeType == 1) { if (object.childNodes[i].nodeName.toUpperCase() == childName) { return object; } if (object.childNodes[i].hasChildNodes()) { var child = findChild(object.childNodes[i], childName, countMatch); if (child) { return child; } } } } } 

I can think of a scenario in which the bounding block of children is deceptively smaller than its own children, in the case of a float or position: absolute element and to fix that it will require a recursive call for all children, but besides this scenario, this should give you the minimum width / height of any item according to the size of their children.

0
source share

Here is the best solution I could come up with.

First, if the DIV depends on its child content, to determine its size, I give it a .childDependent selector and if the div can resize vertically, I give it a .canResize selector.

 <div class="A border childDependent canResize"> <div class="B border canResize"> B </div> <div class="C border canResize"> C </div> <div class="E border canResize"> E </div> <div class="D border canResize"> D </div> </div> 

Here is the fiddle to look at: http://jsfiddle.net/p8wfejhr/

0
source share

All Articles