前端布局基础概述

转载地址:https://mp.weixin.qq.com/s/-LcNZWFFty2lWuND6uuNNA


1. 前言

前端圈有个“梗”:在面试时,问个css的position属性能刷掉一半人,其中不乏工作四五年的同学。在公司一直有参与前端的基础面试,深感这个“梗”不是个玩笑。


然而,我觉得实际比例可能会更高,甚至很多面试官自己也未必真正掌握。因为大部分前端同学,可能不知道初始包含块的概念,或知道但对这个概念理解有误。


造成这种现象的原因主要有两方面,一方面是在介绍这个知识点时,网上有谬误的文章太多,国内外亦如此(MDN也名列其中),导致很多同学被误导(我一开始也是),而且这种错误被代代相传;另一方面可能是我们平时不太注重概念的定义、自身对待知识的态度还不够严谨、缺乏验证精神和系统总结的习惯。


一次偶然的机会,我发现了这种谬误,并找到了W3C组织对初始化包含块的官方定义,也为了让刚入前端圈的同学少走一些弯路,在此我想借本文分享给大家(详述请见5.5. 包含块章节),也系统分享一下,本人在前端布局基础方面积累的浅薄经验。(因为是系统概述,所以篇幅会比较长,希望各位读者有心理准备)


2. 什么是前端布局基础?

前端布局方案主要有三种:

  • 传统布局方案(借助浮动、定位等手段)

  • flex布局方案

  • grid布局方案

这些方案都能够解决布局问题,而且每个方案都有各自的理论基础,那么哪一个方案的基础理论可以称得上是前端布局基础?要回答这个问题,我们还得深入去了解这三种方案的特性。


传统布局方案,需要使用者熟练掌握元素的分类及布局特性、浮动原理和定位原理等众多基础知识,方能在解决各类前端布局问题时游刃有余,这不仅学习成本大,而且实现的复杂度也高,实现的CSS代码也不够精简、优雅。但由于其基础知识来源于CSS2,所以浏览器兼容性最好,对于用户是友好的。


flex布局方案,正是为了解决传统布局方案的种种不便,而提出的一种新型改进方案,它不再需要借助浮动和定位等布局手段,而是通过父元素(flex box)单方面配置相关的CSS属性来决定子元素的布局规则,且在大多情况下无需子元素(flex item)参与,就能完成子元素间的布局问题,不仅学习成本低(公司之前有几个后端工程师亦能快速上手),且大大简化了布局的实现复杂度,CSS代码也更加精炼。美中不足的是IE10才开始支持,且需要使用-ms-前缀(IE11无需)。


虽然现今的手机多使用的是现代浏览器,对flex支持度较好,然而并不是每一款手机都如此:笔者曾在一个移动端项目采用过flex布局方案,然而公司的测试同学在“华为荣耀5”的自带浏览器,检测到无法支持flex布局,我们能够跟测试的同学说,是这款华为手机的浏览器有问题吗?显然不能。于是故笔者在项目早期就及时放弃了flex布局方案,改用传统布局方案实现,避免了后面大规模的改动。


grid布局方案,是由微软提出,相对于传统布局方案和flex布局方案,它是一种二维布局方案,在IE10开始支持,但需要使用-ms-后缀(IE11+不再需要)。


总的来说,这三类方案都能基本解决日常的前端布局问题,且从易用性、灵活性和强大性来说,flex布局和grid布局更是未来的趋势。但是从当前各版本浏览器在用户市场上的使用情况和各方案的浏览器兼容性来看,传统布局方案对用户最友好,具有一定的不可替代性,所以我觉得,传统布局方案是最应该先掌握好的,尤其是对于在to B企业工作的前端同学来说。


所以本文将详细介绍的“前端布局基础”,指的是围绕着“传统布局方案”的众多CSS知识,其主要内容来源于CSS2规范。


3. 为什么要学好前端布局基础?

页面写多了的前端同学,我想应该都会有这样一个深刻的感受:在编写页面时,经常会遇到不同场景的布局问题,我们不仅需要针对特定的场景选定可实现的布局实现方案,而且需要考虑未来可能发生的变化。


而要做好这一点,就需要扎实的前端基础作为依托。


所以在我看来,学好前端布局基础,其目的是为了在面对不同场景的布局问题时,能够提出一种合理的布局方案:既能解决问题,又能最大程度地拥抱变化。


4. 量化布局方案的合理性

前面提到过的“解决问题”、“拥抱变化”,仅仅是合理布局方案的两大核心目标,如果想要让目标更好地落地,我们仍需要一些量化合理性的原则,来提升对目标的方向感,以让目标变得更加可执行。


说到量化“解决问题”这个目标,对于即写即呈现的前端代码来说,我们可以很直观地判断一种方案是否可行,所以不需要太多的量化手段,我们主要是要量化“拥抱变化”这个目标。


要想量化“拥抱变化”这个目标,我们首先得清楚“变化”有哪些。笔者根据过往的开发经验,将变化分为两大类:一是布局需求的变化,二是运行环境的变化。

而针这这两类变化,我提出如下量化原则:

一、对于布局需求的变化,可以做到:

  • 方便快速定位需修改的位置

  • 能够不花或用最少的修改成本应对变化

二、对于运行环境的变化,可以做到:

  • 在不同浏览器均有正确或良好的显示


如果一个方案能够体现以上几点原则,我认为可以称得上是一个合理的方案。最后,我将布局实现方案的合理性归纳为:方案在满足正确性的前提下,其实现逻辑规范、实现职责分明且拥有良好的浏览器兼容性。


下面我们正式开始介绍与“传统布局方案”相关的布局基础知识。


5. 布局基础要点

5.1. CSS标准盒模型(或W3C盒模型)

一个web页面是由众多html元素拼凑而成的,而每一个html元素,都被解析为一个矩形盒,而CSS盒模型就是这种矩形盒的解构模型。CSS盒模型,它由内到外、被四条边界Content edge、Padding edge、Border edge和Margin edge划分为四个区域:Content area、Padding area、Border area和Margin area,在形状上,Content area(又称content-box)是实心矩形,其余是空心环形(空心部分是Content area),如下图所示:

前端布局基础概述_第1张图片

CSS盒模型-区域划分图


此外,每个区域都有其特定的作用:Content area,是当前元素用来容纳所有子孙元素;Padding area,是当前元素用来隔离自身和子孙元素;Border area是当前元素用来显示自身的轮廓;Margin area,是当前元素用来隔离自身和相邻元素。理解每个区域的作用和职责至关重要,有助于我们写出优雅、清晰的布局代码。

前端布局基础概述_第2张图片

CSS盒模型-区域作用图


而每个区域的尺寸,又分别由特定的CSS属性来控制,如下图所示:

前端布局基础概述_第3张图片

CSS盒模型-属性控制图

这些CSS尺寸属性(width、height、padding、border和margin),相当于一个个hook,我们可以通过设置这些“hook”来达到调整元素尺寸的目的。


5.2. box-sizing(CSS3属性)

5.2.1. box-sizing的作用

box-sizing,顾名思义,其作用与设置CSS box的尺寸大小有关,而CSS box又可细分为:

  • content-box(即content area)

  • padding-box(=content area + padding area)

  • border-box(=content area + padding area + border area)

  • margin-box(=content area + padding area + border area + margin area)

简单来说,box-sizing的作用就是告诉浏览器:CSS属性width和height是用于设置哪一种box的尺寸,在W3C标准中,box-sizing的值仅有content-box和border-box(firefox则额外支持padding-box)。所以,

当box-sizing的值为content-box(默认值)时,有:

width = content-width;

height = content-height;

当box-sizing的值为border-box时,有:

width = content-width + padding-left + padding-right + border-left-width + border-right-width;

height = content-height + padding-top + padding-bottom + border-top-height + border-bottom-height;


关于box-sizing的作用,还有另一种表述:告诉浏览器,是使用W3C盒模型,还是使用IE盒模型。


5.2.2. box-sizing的浏览器兼容性

box-sizing是CSS3属性,在IE8+(包含IE8)开始支持,然而在IE8,box-sizing的值为border-box时,不能与min-width, max-width, min-height或max-height的一起使用,因为IE8对min-*和max-*的解析,仍是作用于content-box,不受box-sizing属性控制。


5.2.3. box-sizing的产生原因

仅仅掌握box-sizing的基础使用,是无法真正理解box-sizing的作用,所以要想把box-sizing用好,我们还得从CSS盒模型的发展史来深入理解box-sizing的产生原因。


在CSS的发展历程中,有两个版本,一个是IE盒模型,另外一个是W3C盒模型。IE盒模型,在IE5-(包含IE5)和navigator4上均有使用;而W3C盒模型,在IE6+(包含IE6)标准模式开始得到支持。两种版本的盒模型,其实在模型结构上是一致的,只是with和height属性的计算规则不一样,其区别,等价于“box-sizing的两个属性值border-box和content-box的区别“,如下图所示:

前端布局基础概述_第4张图片

IE盒模型和W3C盒模型的区别



在了解了CSS盒模型的发展历程,以及后来新增的box-sizing的开始支持时间,我们不难发现:

  • IE5-采用IE盒模型

  • IE6、7的标准模式放弃了IE盒模型,转为使用W3C盒模型

  • IE8+借助box-sizing,又重新提供了对IE盒模型的支持


对于IE盒模型,我们看到了W3C组织先去后留的反复态度,我不禁提出以下两点疑惑:

问题一: 为什么W3C组织在制定盒模型标准时,一开始会放弃IE盒模型,而重新建立以content-box为计算规则的W3C盒模型?W3C盒模型比IE盒模型好在哪里?

问题二:为什么在CSS3中,又重新提供了对IE盒模型的支持(box-sizing设置为border-box),又是基于哪方面的考虑?


关于第一个问题,本人并没有找到相关的官方说明,但我比较认可的一种说法是:

在日常生活中,我们在放东西时,会关心东西放到多大的盒子里面,这里的“多大”,往往指的是盒子的容量,而不是整个盒子的尺寸。而HTML元素也被看成是一个盒子、一个容器,相应地,我们也会更关注其内容区域的尺寸,也更希望对内容区域有更强的控制力。所以,从存储的角度来看,W3C盒模型更符合这种认知,借助width和height,我们可以通过声明的方式,直接设置conent-box的尺寸。而如果采用IE盒模型,我们只能先设置整个盒子的尺寸(border-box),最后由浏览器自动计算出content-box的尺寸,显得对content-box尺寸的控制力较弱。


关于第二个问题,我认为有以下几个原因:

1. 有助于复用基于IE盒模型开发的CSS代码;

2. IE盒模型的“遗老遗少”可以延续计算习惯;

3. 部分html元素,在解析时依然采用IE盒模型的计算规则(这样的元素有select、button),使用IE盒模型有助于保持一致性;

4. 从元素布局的角度来看,IE盒模型的width和height的语义更符合人类的直观认知(盒子的尺寸、轮廓应该以border为界);

5. 在弹性布局和响应式布局场景,IE盒模型比W3C盒模型表现更佳(更容易实现、浏览器兼容性更好),如设置某个元素的宽度始终占当前行总宽度的固定百分比(小于100%),并且该元素拥有固定像素的padding;

举个例子:设置一个元素,其宽度分别为当前行的40%,且该元素的padding固定为10px。

IE盒模型的实现方案:

方案一: 使用一个div即可实现,直接设置width为40%,padding为10px;

W3C盒模型的实现方案:

方案一:使用两个div模拟实现,外层div的width设置为40%,内层div的padding为10px,                   width为auto;

方案二:使用一个div即可实现,但是需要借用CSS3的calc函数,动态计算其内容区域的宽度,即width为calc(40% - 20px), padding为10px;


显然,IE盒模型的实现方案更加简洁,而且浏览器兼容性更好。


对上述两个问题的解答,其实也是对IE盒模型和W3C盒模型的一个比较。我们可以从比较中,明晰两种盒模型各自的优缺点。同时,经过大量的实践经验证明和充分讨论,IE盒模型总体上是优于W3C盒模型,这也是IE盒模型能够“王者归来”,被W3C组织重新启用的真正原因。


于是乎,为了重新在新规范中支持IE盒模型,也为了向后兼容W3C盒模型,W3C组织在CSS3中添加了box-sizing属性,用于切换这两种盒模型。


5.2.4. 对box-sizing的评价

在我看来,在CSS3中添加box-sizing其实是一种比较trick的弥补方式。虽然这种设计能重新提供对IE盒模型的支持,但是在某种程度上,造成了CSS属性width和height具有二义性,使其职责变得不单一。然而这似乎又是最可取的修正方案了,因为在网上已经存在了大量基于W3C盒模型开发的网页,后续的修正方案不得不考虑向后兼容。我们只能在不合理设计的基础上,再次用不优雅的设计来解决新的问题。


如果能够穿越时空,回到W3C组织在讨论“如何设计标准盒模型”时,我认为更合适的设计方案是添加新的属性单独用于设置content-box的尺寸,而保留IE盒模型width和height原来的语义。这样就不会有后来的box-sizing属性。


我猜想W3C组织也想过这种方案,但是当时可能认为:

1. 直接设置元素border-box尺寸的意义不大,且border-box的尺寸设置也能够通过设置content-box的尺寸来实现;(其实同时两种支持content-box和border-box尺寸的设置也无妨,完全可以当做是语法糖)

2. 设置content-box尺寸又属于高频操作,若新增的属性命名为content-width或content-height则显得名称太长;(命名为cwidth和cheight也行)

基于这两点,最终提出了用width和height来设置content-box尺寸的解决方案,也就是如今我们看到的W3C盒模型。


纵观CSS盒模型的发展史和box-sizing的创建原因,感触比较深的就是:不合理的设计并不是总会被修正,因为既有实现的广泛应用,会使得其被继续遵循。而后续的新增设计,也是建立在先前不合理设计的基础上。这是否也验证了黑格尔的哲学名言:存在即合理?


关于对box-sizing的评价和思考,可能显得有一些马后炮,一些猜想也可能只是笔者的凭空臆想,并非W3C组织原意。在这里只是为了分享我对重构的一些思考,也是为了与和我有同样疑惑的同学做个交流。


5.2.5. box-sizing的最佳实践

在这里主要回答三个问题:

问题一:box-sizing的值,取content-box好,还是取border-box值好?

如果最低需要兼容IE6、7,那么box-sizing不可使用,只能使用W3C盒模型;

如果最低只需兼容IE8,那么使用content-box在功能上完全没有问题,只是在一些弹性布局和响应式布局实现上,会稍微麻烦一点;而border-box虽然在这些方面表现更好,但是不能和IE8的min-width、min-height、max-width和max-height四个属性一同使用,使用的话就要稍微注意一下;

如果最低只需兼容IE9,那么本人觉得,全局配置取content-box更为合适,局部配置二者均可。原因如下:

1. CSS3提供了calc函数(IE9+),使得W3C盒模型有了强有力的助攻,在弹性布局和响应式布局的表现,与IE盒模型无异;

2. 默认优于配置原则:我个人认为,“默认优于配置”,特别是在reset.css这种架构级、平台级的配置文件,要尽量避免对未来可能引入的模块有侵入性。譬如,我们在一个项目中时常需要引入第三方组件,如果这个组件没有强声明box-sizing,那么其默认使用的就是W3C标准盒模型,如果在全局的reset.css中设置box-sizing的值为border-box以选用IE盒模型,那么就会影响到这一类默认基于W3C盒模型的第三方组件的样式。这里也给我们提了一个醒,在封装组件时,记得强声明box-sizing,哪怕你使用默认的content-box。


总之,大部分场景二者可以互换,只是使用理念不一样。小部分场景border-box更具优势,但随着calc函数的支持,这种优势已经不再,相反content-box是默认值的优势愈加明显。


我个人建议是:全局使用默认W3C盒模型(你的CSS代码最低能够兼容IE6/7,在IE8也可以和min-*和max-*一起使用),局部场景二者均可(仅把IE盒模型当作是一种布局技巧来使用)。你喜欢全局使用IE盒模型也是可以的,只要确认项目只需要兼容到IE8,即便有可能影响到引入的第三方组件,也是有办法处理的。


问题二:如果想要全局使用IE盒模型,那么在reset.css中,该怎样设置box-sizing?

这里提供一个参考:

html {

    -webkit-box-sizingborder-box;

    -moz-box-sizingborder-box;

    box-sizingborder-box;

}

**:before, *:after {

    -webkit-box-sizinginherit;

    -moz-box-sizinginherit;

    box-sizinginherit;

}

这样设置的好处有:

1. 子元素的盒模型类型,默认由父元素决定,方便组件统一设置;

2. 支持低版本的浏览器:Safari (< 5.1), Chrome (< 10), and Firefox (< 29);


问题三:Bootstrap3开始,全局使用IE盒模型(box-sizing取border-box),又是基于怎样的考虑?怎么协调好与基于标准盒模型开发的第三方组件的关系?

众所周知,BS2还考虑对IE7的兼容,而BS3彻底放弃了对IE7的兼容,并将box-sizing设置为border-box。关于这一点,可见“Bootstrap 3 released”官方发布的change list,摘录如下:

从以上直白的表述中:Better box model by default(默认使用更好的盒模型),我们可以看出BS作者是IE盒模型的拥趸。作者也把理由罗列了出来,其核心内容也是如前面所提到的,IE盒模型在响应式布局上的良好表现。补充的一点是,如果不全局设置border-box,而每个组件及其子组件单独设置,维护起来将是个梦魇(作者在官方编号为12351的issure中有提到)。


而关于BS如何处理好与基于标准盒模型开发的第三方组件的关系,亦可参见编号为12351的issue"Move away from * {box-sizing: border-box } to play nice with 3rd party scripts"

作者在issue中,霸气又委婉地回应:

1. BS并不考虑对第三方组件和框架的支持。作者委婉地说,BS是一个大项目,活跃维护者也主要是四个人,顾不来所有人的需求啊~(但感觉作者是在说,BS是个又大又全的框架,你丫还搞第三方组件干嘛呀)

2.IE盒模型,用了大家都说好,为什么第三方组件不转过来支持IE盒模型啊(果然是铁粉)


本章节从box-sizing的作用、浏览器兼容性、产生原因、评价和最佳实践这5个切入点,来讲解box-sizing属性,以期加深各位同学对这个属性的理解和掌握。特别要强调的一点是,如果刚接手某个项目,在编写CSS代码前,先看看项目是否有全局配置box-sizing,并根据具体的取值来选用相应的尺寸计算规则。


5.3. 元素的分类及其布局特性

5.3.1. 元素的分类

从元素的布局特性来分,主要可以分为三类元素:block-level(块级)元素、inline-level(行内级)元素和inline-block-level(行内块级)元素,我们可以对其下个定义:

5.3.1.1. 块级元素

display属性取block、table、flex、grid和list-item等值的独占一行显示的元素。

5.3.1.2. 行内级元素

display属性取inline值的可在同一行内排列显示的元素。

5.3.1.3. 行内块级元素

display属性取inline-block、inline-table、inline-flex和inline-grid等值的兼具块级元素和行内级元素布局特性的元素。


友情提示:

1)关于各类元素display的取值,实际已全部罗列,但为了保证定义能拥抱变化(未来可能引入新的display属性值),在罗列时使用了等字;

2)w3c官方文档,把display属性值为inline、inline-block、inline-table的元素,统称为inline-level元素,我不太喜欢也不太认可这种泛泛的分类,本文重新定义了一个“inline-block-level元素”的概念,来对“inline-level元素”进行了细分,并将inline-blocks、inline-tables单独分类为inline-block-level元素,原文档如下:“The following values of the 'display' property make an element inline-level: 'inline', 'inline-table', and 'inline-block'.


5.3.2. 元素的布局特性

5.3.2.1. 块级元素(block-level)的布局特性

对于块级元素,有如下几个布局特性:

  1. 独占一行(width默认为100%,height为0);

  2. 可以设置任何尺寸相关的属性(width、padding、margin和border);

5.3.2.2. 行内级元素(inline-level)的布局特性

在讲行内级元素的布局特性之前,我们先了解一下行内级元素的分类,其可再细分两类元素:

1)可置换行内元素

在MDN中,其对“可置换行内元素”的定义如下:

前端布局基础概述_第5张图片

按字面翻译,“可置换行内元素”,是展示内容不在CSS作用域内的元素。这句话是不是不好理解?我们可以换另外一种方式理解:“可置换行内元素”,是这样一类元素,其展示的内容是通过元素的src、value等属性或CSS content属性从外部引用得到的,可被替换的。随着内容来源或内容数量的变化,可置换元素本身也会有水平和垂直方向上尺寸的变化。典型的可替换元素有 、 、  和 ,表单类的可替换元素有