CSS:宽度和高度

CSS像艺术家一样优雅,又像工程师一样严谨。

自适应的宽度

大音希声,大象无形。世界的复杂与变化、奇妙与精巧往往藏在最简单的一片虚无中,就像成熟的CSS程序员会用 width:auto 来设置宽度一样。

width:auto说白了就是自适应宽度,但究竟什么是“自适应”,就这包含了几种类型:

  • 充分利用可用空间(流特性)。
  • 收缩到合适(包裹性)。
  • 收缩到最小(一柱擎天)。
  • 超出容器限制(溢出来了)。

外部尺寸和内部尺寸

外部尺寸就是盒子的尺寸取决于外部,就像对盒子尺寸加了一个约束或者外力使得盒子具有了某个尺寸;而内部尺寸则取决于内部,就是看盒子里装的是什么,装的东西大盒子就被撑大,装的东西小盒子就收缩到包裹着盒子。

对于以上说的几种“宽度自适应”的的表现类型,也就只有“充分利用可用空间(流特性)”是外部尺寸,其他都是内部尺寸。

外部尺寸

流宽度

在自适应宽度中,外部尺寸与流特性是挂钩的,流特性就是在文档中,块状元素(如div)是像水流在容器中一样铺开的:

对于一个div标签,如果不给它设置宽度(即width:auto),div的宽度会自动铺满页面,即100%页面宽度。将它放入其他容器也是如此,这就是“自适应”。

因此很多情况下,对于宽度要铺满容器的元素,完全可以考虑把它转换为块状元素(display:block),然后不对它设置宽度,使用其天然的流特性来达到目的。如果为其设置了宽度,反而多此一举了,破坏了其流特性,反而可能产生不必要的麻烦(比如把padding之类的考虑进去,进行各种宽度计算,费力又难以维护)。

格式化宽度

格式化宽度只是非替换元素(如div、p等,见另一篇文章)在绝对定位或固定定位模型中,即设置了 position:absolute 或 position:fixed 的情况下。

这样的情况下本来应该表现为内部尺寸的,但有一种情况下其会表现为外部尺寸,即同时再设置left和right属性。那么这个盒子会寻找离它最近的具有定位特性(指非 position:static 的元素)的祖先,依据这个祖先计算宽度:留出left和right的距离,剩下的部分铺满。就像这样:

内部尺寸

所谓内部尺寸,就是以容器里的内容的尺寸来决定尺寸。

包裹性

这就是另一种自适应了,上面的流体特性是自适应使元素在容器里铺开,这里就是自适应收缩到合适,一个是往大了适应,一个是往小了适应。

这里自适应的具体内容就是指,元素尺寸由内部元素的尺寸决定,元素紧紧包裹着内部元素,但不会超过外部容器的尺寸。最典型的是inline-block元素。

一个最常见的例子——按钮,主要有以下两种使用形式:

1
2
<button>按钮</button>
<input type="button" value="按钮">

按钮就是最常见的inline-block元素,它的具体表现是:按钮文字越多,按钮就越长,宽度达到它外部容器的宽度时,它会自动换行!

如果没有注意到按钮会自动换行,可能的原因是你没碰到过足够多字数的按钮,或者你用的是input按钮(input的默认white-space属性值是pre,这个属性会让它不换行)。

例子:

1
2
3
<div style="width: 200px;border:red solid 2px;padding: 5px;">
<button onclick="this.innerHTML+='点我'">点我</button>
</div>

代码效果:

那么这里有一个巧妙使用包裹性的小例子,比如我们的需求是在一个容器中有若干文字,文字少的时候让文字居中显示,而文字多了就让它自动左对齐显示:

HTML代码:

1
2
3
4
<div class="box">
<p id="conMore" class="content">文字内容</p>
</div>
<p><button id="btnMore">更多文字</button></p>

CSS代码:

1
2
3
4
5
6
7
8
9
.box {
padding: 10px;
background-color: #cd0000;
text-align: center;
}
.content {
display: inline-block;
text-align: left;
}

效果就像下面一样:

文字内容

首选最小宽度

试想,假如对一个容器设置 width:0px ,但是它里面有文字,怎么办?CSS最初是为图文排版服务的,当然不可能真的将容器宽度变为0,此时容器表示的宽度为首选最小宽度,也就是尽可能地压缩宽度,能压缩到的最小的宽度,就像这样:

1
<div style="width: 0px;">I love you very much. </div>

这是把能拆行的地方都拆行了,以能够让宽度最小,当然这是对于英文,只能按单词拆行,而汉字则可以每个字都拆行,因此才会出现那种在表格里表格文字多但格子窄的一柱擎天的现象了:

对于表格这种,就不是设置了 width:0px ,而是整个表格设置了宽度,单元格为 width:auto 但是内容太多,导致每列分配宽度时使该单元格出现了这种现象。

宽度分离

当一件事情的发展可以被多个因素所左右的时候,这个事情最终的结果就会充满变数而不可预期。

就像元素的宽度一样,width只决定了内容区域的宽度,如果设定了padding和border,元素的实际宽度会更大,这么多的因素都影响着元素的实际宽度,使得优雅地维护元素的宽度成为了必须要考虑的事情。

为什么宽度分离

计算机领域,一旦提起“分离”,一定有便于维护的作用在里面,比如前后端分离、接口与实现分离、样式与行为分离等等,宽度分离也是为了更好地维护代码。

如果不对宽度进行维护,本来设好了元素的width,突然我们需要对其增加padding或border就会使它的实际宽度增大,可能会挤占别的元素的空间,使得布局错乱。

此时代码将变得十分不好维护——你需要一个个重新计算元素的width并为它们重新设置。而很不错的一种维护宽度的方式就是宽度分离。

如何宽度分离

宽度分离实际上还是利用了宽度的自适应特性,即:为需要维护宽度的元素在外单独使用一层div来控制宽度,这层div不使用padding、border,宽度只通过width指定,这样减少了要控制的变量,使得宽度更容易维护,而原本的元素在这个已设置好宽度的div中使用 width:auto 来设置自适应的宽度,使得元素自然地在容器中铺开,这样再怎么设置元素的padding和border都不会导致元素整体宽度改变从而挤占其他元素的空间了。

总结下来即:父元素定宽,子元素宽度自适应

其他解决方案

box-sizing

有一种无需计算宽度又无需额外一层标签的方法是使用box-sizing属性。这个属性可以规定width和height作用的细节——即作用在盒模型的哪一部分。

该属性的默认值是content-box,即内容盒子,还支持另外一个值即border-box,也就是边框盒子,此外没有其他支持的值了。但对于实际使用来说,仅仅一个border-box其实也够用了,前面所说的宽度分离实际上也是在间接地控制border-box。

滥用box-sizing

有人认为既然如此就不单独给元素设置 box-sizing:border-box 了,直接设置全局重置不就行了:

1
* { box-sizing:border-box }

这样所有的尺寸都作用于边框盒子就不需要宽度分离这么麻烦了。

但是这样会导致滥用,因为大多元素是用不着这样设置的,会产生不必要的消耗,对于普通的内联元素来说,无论box-sizing是什么值对渲染都没有影响,为它们额外设置这样的属性是一种额外消耗。

还有就是这样并不能解决所有的问题,比如margin问题,对于这样设置的元素,margin还是要单独计算,而宽度分离策略可以解决所有宽度问题。

必要的使用场景

有时候是必须要用box-sizing的场景,即对于可替换元素希望其width为100%的时候。

块级元素宽度自适应会铺满容器,但是有一些例外,就是可替换元素,它们无论display是inline还是block都不会自动铺满容器,而是有它们默认的宽度或宽度由其内容决定,这时希望其具有100%宽度就不能像平时一样将display设为block来解决了,而是要手动设定 width:100% 。

但对于一些可替换元素,比如textarea文本域,它是有border的,同时还要有padding,否则光标会顶着边框,体验不好,这时想要就需要设定 box-sizing:border-box 之后在设置 width:100% 才能得到我们希望的效果。

高度

宽度是稀缺的,高度是无限的。

在HTML中文档默认都是向下翻页,一个文档想多长都没有关系,因此高度是无限的,但宽度就只有一个屏幕的宽度大小(除非你想让页面有横向滚动条)。

高度自适应

因为高度是无限的,因此高度的自适应就简单多了,就看元素内部的内容能撑起多大的高度,那么自适应的高度就是多少。

不过和格式化宽度类似,高度也是有格式化高度的。

高度100%

宽度设定为100%无论如何都会有一个具体的计算植,而高度设定为100%可能得不到结果

如何让高度100%能够生效呢?主要有两种办法:

  • 让它的父级元素有一个明确的可以生效的高度值,比如它的父级设定了 height: 600px 。
  • 使用绝对定位,元素绝对定位必定能计算出一个高度值,哪怕其父元素没有明确的高度。

但同时需要注意绝对定位时的百分比高度和非绝对定位时的计算方法不一样,绝对定位时百分比高度是相对于父元素的padding-box计算的,而其他情况下是相对于content-box。

总结来说就是:百分比高度在绝对定位时支持隐式高度计算

在实际应用中有这样的例子:

HTML代码:

1
2
3
4
5
<div class="box">
<a href="javascript:" class="prev" title="上一张">上一张</a>
<a href="javascript:" class="next" title="下一张">下一张</a>
<img src="/images/common/l/1.jpg">
</div>

CSS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.box {
display: inline-block;
position: relative;
}
.prev,
.next {
width: 50%; height: 100%;
position: absolute;
top: 0;
opacity: .5;
}
.prev {
left: 0;
}
.next {
right: 0;
}

这个例子可以做到常见的点击图片左半边切换上一张,点击右半边切换下一张的效果,就是通过两个100%高度,50%宽度的a标签分别绝对定位在左右两半边覆盖在图片上来实现的。

宽高的max和min

萌新不会经常使用这些属性,只有随着CSS技能的深入,能够考虑还原性的同时还兼顾扩展性和适配性的时候,它们才会被用得越来越多。

宽高范围

宽高的max和min即max-width、min-width、max-height、min-height四个属性,它们可以分别为宽高设置可伸缩的范围,比如一个页面可能在1200~1400像素的不同屏幕上显示,就可以设置:

1
2
3
4
.container {
min-width: 1200px;
max-width: 1400px;
}

这样会使得容器最小宽度不低于1200px而最大宽度不超过1400px,在这个宽度范围内自适应。

再比如公众号文章中有用户上传的图片,为了防止其尺寸过大,就可以设置:

1
2
3
4
img {
max-width: 100%;
height: auto! important;
}

这样可以防止图片过大超过容器宽度,同时图片不大的时候又可以按原始宽度显示。这里的height是为了保证max-width生效时,图片不会变形(防止在别处为图片设定了height)。

默认值

width和height的默认值是auto,即在不显式设定尺寸时为自适应。

min-width和min-height的默认值为auto,而max-width和max-height的默认值为none。

min-width和min-height的默认值为auto意味着它们的渲染规则和width、height是一样的,也就是元素的width、height是多少,它们的min-width、min-height就是多少,相当于min-width、min-height总是和width、height相等,也就是min-width、min-height此时相当于没有(因为存在不存在没什么区别)。

而max-width和max-height的默认值之所以为none,是因为按正常的逻辑,默认情况下应当不为宽高设置上限的,因此默认值为none。

优先级

max覆盖

以宽度为例,高度是一样的道理。

当两者冲突时max-width会覆盖width,在任何情况下都会覆盖,哪怕width是行内属性,哪怕width设置了!important。因为如果max-width不能覆盖width,那么这个上限的规定就没有了意义。

min覆盖

所谓一物降一物,max-width与width冲突时,max-width无条件覆盖width,而当min-width与max-width冲突时,后者又会被前者覆盖。

即min-width会覆盖max-width,当设置的上限比下限还低时,生效的是下限。

展开与收起动画

如果要实现展开与收起动画,大家比较常见的是使用JS实现,而使用原生CSS也是可以实现的。

使用height实现

我们考虑使用height实现展开与收起动画,需要考虑的是动画的起点和终点。

以展开为例,动画的起点是 height:0 ,而终点应当是元素本来的高度,那么问题来了,怎么对于元素来说怎么确定它的高度呢?对于一个高度确定的元素还好,但对于高度为auto的,我们设置:

1
2
3
4
5
6
7
8
.element {
height: 0;
overflow: hidden;
transition: height .25s;
}
.element.active {
height: auto;
}

会发现动画是不起效的,没有补间,原因是因为auto是个特殊值,浏览器要计算补间动画,需要在起点和终点之间插值,可是height从0到auto是无法计算的,因为auto不是一个确切的值,浏览器没有这样计算的依据。

这时就可以使用max-height来实现了:

1
2
3
4
5
6
7
8
.element {
max-height: 0;
overflow: hidden;
transition: max-height .25s;
}
.element.active {
max-height: 666px;
}

这里把max-height设定一个足够大的值,这样初始状态下受max-height为0的限制,之后慢慢解开这个限制,直到元素恢复其原来的大小。

但是要注意这个足够大的值也不能太大了,因为这里的动画计算的是max-height值,假如元素原本就只有100px,却设置了终点为1000px,如果动画用时1秒,那么就只有前0.1秒元素有变化,后0.9秒都是max-height值再变化,反过来收起元素的时候,前0.9秒没有动静,最后0.1秒迅速收起,好像动画有延迟一样。

即终点max-height要设定一个足够安全的最小值




* 你好,我是大森。如果文章内容帮到了你,你可通过下方付款二维码支持作者 *