为一个元素设置长度(高度或宽度)减去另一个元素的可变长度,即 calc(x - y),其中 y 未知

Posted

技术标签:

【中文标题】为一个元素设置长度(高度或宽度)减去另一个元素的可变长度,即 calc(x - y),其中 y 未知【英文标题】:Setting a length (height or width) for one element minus the variable length of another, i.e. calc(x - y), where y is unknown 【发布时间】:2016-01-12 19:07:54 【问题描述】:

我知道我们可以在定义长度时使用calc

flex-basis: calc(33.33% - 60px);
left: calc(50% - 25px);
height: calc(100em/5);

但是如果长度是可变的呢?

height: calc(100% - <<header with variable height>>);

width: calc(100% - 50px - <<box with variable width>>);

在 CSS 中是否有标准的方法来做到这一点?

我知道使用 flexbox 和表格可以完成整个任务,但我想知道 CSS 是否提供了一种更简单的方法。 Flexbox、表格和简单的 javascript 是可接受的替代方案。

height demo

width demo

【问题讨论】:

这是个好问题。 对于width,表格布局可能有效。表格单元格宽度由浏览器根据内容和同级元素自动计算。我没有准备好一个例子。对于height,我认为不会。 我认为没有 JavaScript 就无法做到这一点。 对于任何想要非 flex 版本的人(正如我所说的那样)并且可以生存 display: table,这里有一个(结合了 2 个演示示例进入 1) jsfiddle.net/LGSon/f01go4wf/8 对于height,我通常将一个元素放在另一个元素中,like here。希望对您有所帮助:) 【参考方案1】:

Flexbox 可以做到这一点。

支持 IE10 及更高版本。

JSfiddle Demo

* 
  margin: 0;
  padding: 0;

html,
body 
  height: 100%;

#container 
  height: 100%;
  display: flex;
  flex-direction: column;

#top 
  background-color: lightgreen;

#bottom 
  background-color: lightblue;
  flex: 1;
<div id="container">
  <div id="top">green box variable height</div>
  <div id="bottom">blue box no longer overflows browser window</div>
</div>

【讨论】:

这真的行不通。这是填充其容器的一些 HTML 的 2 个示例。 Example #1。 Example #2。一旦放置在 flexbox 中,它们就不再正确地填充容器。 The workaround is here,涉及position: absolute;的解决方案。上述解决方案使浏览器处于破坏各种内容的状态。【参考方案2】:

您可以使用 CSS 表格:

.wrapper 
  display: table;
  width: 100%;
  margin: 15px 0;


.horizontal.wrapper > div 
  display: table-cell;
  white-space: nowrap; /* Prevent line wrapping */
  border: 1px solid;

.left  width: 100px  /* Minimum width of 100px */
.center  width: 0;   /* Width given by contents */

.vertical.wrapper  height: 200px; 
.vertical.wrapper > div 
  display: table-row;

.vertical.wrapper > div > span 
  display: table-cell;
  border: 1px solid;

.top     height: 100px;  /* Minimum heigth of 100px */
.middle  height: 0;      /* Height given by content */
.bottom  height: 100%;   /* As tall as possible */
<div class="horizontal wrapper">
  <div class="left">100px wide</div>
  <div class="center">Auto width, given by contents</div>
  <div class="right">Remaining space</div>
</div>
<div class="vertical wrapper">
  <div class="top"><span>100px tall</span></div>
  <div class="middle"><span>Auto height, given by contents</span></div>
  <div class="bottom"><span>Remaining space</span></div>
</div>

水平的情况也可以用浮点数来实现:

#wrapper, .right  overflow: hidden;  /* Establish BFC */
#wrapper > div  border: 1px solid; 
.left, .middle  float: left; 
.left  width: 100px 
<div id="wrapper">
  <div class="left">100px</div>
  <div class="middle">Auto width, given by contents</div>
  <div class="right">Remaining space</div>
</div>

【讨论】:

【参考方案3】:

更新

正如我之前的commented 一样,除了flex,这也可以使用display: table 来解决,这是我展示的fiddle demo。

如果垂直演示也需要固定顶部,这里是我原来的display:table 版本的更新:fiddle demo

有时我无法(或不想)使用 flex 或表格,我时不时地考虑使用 css calc() 和 css attr()。

虽然两者都不足,因为calc() 只能使用+-*/,而attr() 只能返回一个字符串值,而calc() 无法计算。

我的建议是使用纯 javascript,基于这两种方法在某些时候可能会被扩展,以便我们可以更好地利用它们。

这就是我希望看到它们工作的方式;

width: calc(100% - attr(this.style.left))

但是因为他们没有,我也不能将它添加到我的 css 中,因为它不能正确验证(甚至可能会破坏解析,谁知道)我添加了一个变体作为元素的属性,而不是,有一些怪癖使它更容易计算。

在这种情况下(2 个演示)它看起来像这样:

//height
<div id="bottom" data-calcattr="top,height,calc(100% - toppx)">...</div>

//width 
<div class="box right" data-calcattr="left,width,calc(100% - leftpx)">...</div>

连同下面的脚本,它绝不是在所有属性组合上完全开发/测试的,它确实调整了 div 的大小。

简而言之,当运行时,它获取属性,将其拆分为一个数组,获取第一个项目值作为从哪个属性读取,第二个到哪个属性设置,第三个读取值被插入/替换并分配给要设置的属性(嗯,仍在研究一种更好的方式来表达这一点,但希望脚本足够清楚地说明正在发生的事情)。

这里是a fiddle,展示了高度和宽度演示,集成,使用相同的脚本。

function calcattr() 
    var els = document.querySelectorAll('[data-calcattr]');
    for (i = 0; i < els.length; i++) 
        var what = els[i].getAttribute('data-calcattr');
        if (what) 
            what = what.split(',');
            var rect = els[i].getBoundingClientRect();
            var parentrect = els[i].parentNode.getBoundingClientRect();
            var brd = window.getComputedStyle(els[i].parentNode,null).getPropertyValue('border-' + what[0] + '-width');            
            what[2] = what[2].replace(what[0],parseInt(rect[what[0]]-parentrect[what[0]]) - parseInt(brd));
            els[i].setAttribute("style", what[1] + ":" + what[2]);
        
    

【讨论】:

【参考方案4】:

我正在寻找简单便携的东西。以同样的方式一个 CSS 属性可以很容易地跨文档应用,我正在寻找 在此功能的易用性方面类似。

...隔离修复是首选。

水平:

这只能使用 CSS 来实现。由于您不喜欢 flex 布局解决方案,因此下一个最佳选择是表格布局。

一个简单的 CSS sn-p,您可以放入您的项目(并完成),如下所示:

div.flexh 
    display: table; box-sizing: border-box; padding: 0; margin: 0;

div.flexh > div  
    display: table-cell; width: auto; 
    box-sizing: border-box; vertical-align: middle;

div.flexh > div:first-child 
    /* Override your custom styling below */
    min-width: 75px; width: 75px; max-width: 75px; 

div.flexh > div:last-child  width: 100%; 

然后,您可以根据网站要求将特定于网站的样式添加到此基本 CSS 中。比如nowrap等等。

此解决方案的两个明显优势是:

    你不需要改变你的标记,也不需要用类来装饰所有的孩子。只需将 flexh 类应用到您的父母 div 即可。

需要最少的标记:

<div class="flexh">
    <div>...</div>
    <div>...</div>
    <div>...</div>
</div>
    您不仅限于三列。您可以根据需要拥有尽可能多的列。第一个将具有固定宽度,最后一个将是灵活的,中间的所有列都将获得基于内容的宽度。

演示小提琴:http://jsfiddle.net/abhitalks/qqq4mq23/

演示片段:

div.flexh 
    display: table; box-sizing: border-box; padding: 0; margin: 0;
    /* Override your custom styling below */
    width: 80%; border: 2px solid black;
    border-right: 2px dashed black; 
    font-size: 1em;

div.flexh > div  
    display: table-cell; width: auto; 
    box-sizing: border-box; vertical-align: middle;
    /* Override your custom styling below */
    background-color: lightgreen; border: 1px solid #ddd;
    padding: 15px 5px;

div.flexh > div:first-child 
    /* Override your custom styling below */
    min-width: 75px; width: 75px; max-width: 75px; 
    background-color: orange;

div.flexh > div:last-child 
    width: 100%;
    /* Override your custom styling below */
    background: skyblue;
<div class="flexh">
    <div>75px Fixed Width</div>
    <div>Variable Content Width</div>
    <div>Flexible Remaining Width</div>
</div>

<hr/>
<div class="flexh">
    <div>75px Fixed Width</div>
    <div><img src='//placehold.it/128x48/66c' /></div>
    <div>Flexible Remaining Width</div>
</div>

<hr/>
<div class="flexh">
    <div>75px Fixed Width</div>
    <div>Variable TextWidth</div>
    <div>
        <img src='//placehold.it/128x48/66c' />
        <p>Variable ContentWidth</p>
    </div>
    <div>Flexible Remaining Width</div>
</div>

垂直:

如果没有flex 布局,这有点难以实现。 表格布局在这里不起作用,主要是因为table-row 不会按照您的用例要求保持固定高度。 table-rowtable-cell 上的 height 仅表示所需的最小高度。如果空间受限,或内容超出可用空间,则cellrow 将根据内容增加其高度。

按照这里的规范:http://www.w3.org/TR/CSS21/tables.html#height-layout

'table-row' 元素的盒子的高度在用户计算一次 代理具有可用行中的所有单元格:它是 行的计算“高度”,每个单元格的计算“高度” 行,以及单元格所需的最小高度 (MIN)...

...单元格框的高度是所需的最小高度 内容

这个效果可以看这里:http://jsfiddle.net/abhitalks/6eropud3/

(调整窗格大小,你会看到第一行的高度会增加,因为内容无法适应指定的高度,从而达不到目的)

因此,您可以使用内部标记(如 div 元素)间接限制高度,或者放开表格布局并计算灵活布局的高度。在您的用例中,您不希望更改标记,因此我不建议使用内部标记。

这里最好的办法是使用经过时间考验的普通block-level divs 模型,并计算灵活模型的高度。正如您已经发现 CSS 无法实现的那样,您需要一个小的 JavaScript sn-p 来为您做到这一点。

一个简单的 JavaScript sn-p (no jQuery),您可以将其包装在 window.load 中并放入您的项目(并完成),如下所示:

var flexv = document.querySelectorAll('div.flexv');
/* iterate the instances on your page */    
[].forEach.call(flexv, function(div) 
    var children = [].slice.call(div.children), // get all children
        flexChild = children.splice(-1, 1),     // get the last child
        usedHeight = 0, totalHeight = div.offsetHeight;

    children.forEach(function(elem) 
        usedHeight += elem.offsetHeight; // aggregate the height
    );
    /* assign the calculated height on the last child */
    flexChild[0].style.height = (totalHeight - usedHeight) + 'px';
);

CSS sn-p 或多或少类似于水平的无表格布局,您也可以将其放入项目中并添加额外的特定于站点的样式。所需的最少标记保持不变。

演示小提琴 2:http://jsfiddle.net/abhitalks/Ltcuxdwf/

演示片段:

document.addEventListener("load", flexit);

function flexit(e) 
	var flexv = document.querySelectorAll('div.flexv');
	[].forEach.call(flexv, function(div) 
		var children = [].slice.call(div.children), 
			flexChild = children.splice(-1, 1), 
			usedHeight = 0, totalHeight = div.offsetHeight;
		children.forEach(function(elem) 
			usedHeight += elem.offsetHeight;
		);
		flexChild[0].style.height = (totalHeight - usedHeight) + 'px';
	);
div.flexv 
    display: inline-table; box-sizing: border-box; padding: 0; margin: 0; 
    overflow: hidden;
    /* Override your custom styling below */
    height: 320px; width: 20%; border: 1px solid black; font-size: 1em;
    margin: 8px;

div.flexv > div  
    display: block; height: auto; box-sizing: border-box; 
    overflow: hidden;
    /* Override your custom styling below */
    background-color: lightgreen; border: 1px solid #ddd;
    padding: 5px 15px;

div.flexv > div:first-child 
    /* Override your custom styling below */
    min-height: 36px; height: 36px; max-height: 36px; 
    background-color: orange;

div.flexv > div:last-child 
    height: 100%;
    /* Override your custom styling below */
    background: skyblue;
<div class="flexv">
    <div>36px Fixed Height</div>
    <div>Variable Content Height</div>
    <div>Flexible Remaining Height</div>
</div>

<div class="flexv">
    <div>36px Fixed Height</div>
    <div><img src='//placehold.it/64x72/66c' /></div>
    <div>Flexible Remaining Height</div>
</div>

<div class="flexv">
    <div>36px Fixed Height</div>
    <div>Variable Text Height</div>
    <div>
        <img src='//placehold.it/72x48/66c' />
        <p>Variable Content Height</p>
    </div>
    <div>Flexible Remaining Height</div>
</div>

注意:正如@LGSon 所指出的,用于演示的display: inline-table 不能很好地与Firefox 配合使用。这仅用于演示,应根据您的用例替换为 blockinline-block


【讨论】:

由于 FF 不喜欢在表格中包含除单元格之外的任何内容,因此垂直解决方案在其上效果不佳。有没有办法用你的脚本来弥补? @LGSon:感谢您的观察。 (1) 在这种情况下,FF 似乎没有遵循规范。规范明确指出,当缺少表格元素时将生成匿名框。参考here 和here。 (2) 在任何情况下,基于表格的解决方案都不适用于 Op 的用例。 inline-table 部分是先前尝试的遗留物。出于演示目的,它应该是inline-block。很快就会解决答案。 不客气。 FF 问题来自this post,它实际上使用了inline-block,所以他们也可能会弄乱你的演示(在 FF 上)。 我使用display: table 制作了问题中的两个演示,所以你的意思是它不起作用,就像我的那样? @LGSon:我只看到one demo of yours 并且在垂直布局中没有固定的顶部。如果我遗漏了什么,请告诉我。【参考方案5】:

在 CSS 中

虽然我从未尝试过,但我相信这会奏效:

.top 
    height:13px;


.main 
    height:calc(100% - var(height));

http://www.creativebloq.com/netmag/why-you-need-use-css-variables-91412904


在 SASS 中

$top_height: 50px

.main 
    height: calc(100% - $top_height)

Sass Variable in CSS calc() function

【讨论】:

这两个样本不是预处理器吗? .. 如果是这样,当浏览器调整大小时,这将如何工作? ..你能做一个工作演示吗? @LGSon 非常适合重新调整大小,您应该始终使用 % 或 em/ex 我知道,但在这种情况下,我看不到它有效,而且我对所有可以以一种或另一种方式解决这个问题的解决方案非常感兴趣,我想如果你可以展示你是如何完成这项工作的。 我不能证明你的解决方案有效,但是是的,我试过了,但它不起作用。而“var”技术是实验性的,只能在 FF 中使用,这在今天已经毫无用处了。我认为真正测试您的建议是一个好主意,现在它确实有效,然后再将其添加为答案。再说一遍,您能否展示一下您是如何完成这项工作并解决 2 个演示的? CSS variables 必须以-- 为前缀,例如--height: 13px, height: calc(100% - var(--height))【参考方案6】:

在容器 css 的这两种情况下,您都应该放置:

#container 
    overflow: hidden;

但是,它会隐藏溢出容器的信息。我认为这就是重点,既然你放了white-space: nowrap;,就意味着你不想改变高度,所以你必须隐藏不适合容器的文字。

【讨论】:

以上是关于为一个元素设置长度(高度或宽度)减去另一个元素的可变长度,即 calc(x - y),其中 y 未知的主要内容,如果未能解决你的问题,请参考以下文章

将元素高度/宽度设置为百分比和像素值的组合[重复]

js 中与元素有关的高度

flex布局怎么设置其中元素的宽度

box-sizing属性

在CSS中如何让父元素的宽度等于其子元素的宽度之和?

为啥在html页面设置<span>的高度和宽度没有效果呢