计算DIV元素的最大/最小高度

时间:2014-12-16 22:02:17

标签: javascript jquery html css

问题: 给定具有固定高度的DIV元素,其包含相对于其高度确定大小的未知数量的子元素,计算DIV可以调整大小的最大/最小高度,而不违反其子项的任何最大/最小值元件。

示例 找到DIV A的最大/最小高度

答案

最低:150px

最大值: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>
&#13;
&#13;
&#13;

其他信息: 我目前已经尝试使用遍历DIV的DOM树的算法,并使用元素偏移创建表示元素的空间定位的对象图。下面是一个基本的算法,它检查元素的空间关系,允许在边缘之间进行10px的扩展,以便触摸&#39;。

只要它们是开源的,就允许使用jQuery和其他库。

&#13;
&#13;
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;
}
&#13;
&#13;
&#13;

编辑:修正了CSS错误。这是一个更新的小提琴 http://jsfiddle.net/zqnscmo2/

编辑2:尝试在CSS / HTML的问题空间中考虑更多的图形问题。想象一下,CSS和HTML用于描述每个DIV是顶点的图形。两个顶点之间存在边缘

1。)如果HTML元素的边界是rectA.top≈rectB.topOR 2.)如果边界rectA.bottom≈rectB.top

,则存在边缘

每个顶点有两组独有的边,集A包含符合标准1的所有边。集B包含符合标准2的所有边。因此,您可以遍历图并找到最小和最大路径,这应该是父母DIV的最大/最小身高。

这是我提出的用于确定内部内容的最大/最小高度的算法。我对不那么复杂的解决方案非常开放。

3 个答案:

答案 0 :(得分:1)

这就是我的想法:

  1. 找到具有明确身高的最近的祖先
  2. 查找百分比高度的所有祖先,并计算最近的一个祖先的高度,以找到可用的高度。让我们称之为祖先NAR和身高NARH。
  3. 找到元素距其父元素顶部的距离(使用getBoundingClientRect)。叫它DT
  4. 从DT中减去NAR的上边界。叫这个A.
  5. 您的最大身高应为NARH-A
  6. 类似的事情可以做到最低限度。

    更新:哦,凯,我实施了这个想法,它的确有效!它考虑了很多垃圾,包括边距,边框,填充,滚动条(甚至是自定义宽度),百分比宽度,最大高度/宽度和兄弟节点。看看这段代码:

    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"
    }
    

    我可能会把它放在一个npm / github模块中,因为它似乎应该是天真可用的东西,但不是并且需要一些工作来做正确的事情。

答案 1 :(得分:0)

如果我理解你的问题,这会有用吗?

// - 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's current width is: " + totalWidth + " and it's child's largest width is: " + maxWidth);
    alert("object's current height is: " + totalHeight + " and it's child's 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;
                }
            }
        }
    }
}

我可以想到一个场景,其中子对象的边界框看起来比它自己的孩子小,在浮点数或位置:绝对元素的情况下,并且要确定需要对所有子对象进行递归调用,但除了这种情况,这应该根据孩子的大小给你任何元素的最小宽度/高度。

答案 2 :(得分:0)

这是我能提出的最佳解决方案。

首先,如果DIV依赖于它的子内容来确定它的大小,我给它一个选择器.childDependent,如果div可以垂直调整大小,我给它选择器.canResize。

<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>

这是一个小提琴: http://jsfiddle.net/p8wfejhr/