浮动布局为什么被淘汰?从圣杯布局开始理解

31 阅读14分钟

浮动布局为什么被淘汰?从圣杯布局开始理解

(墙裂建议搭配这个食用!本文章是我作为web初学者的期末作业的一部分……欢迎大家前来批改!)gitee.com/jeyi08/floa…

一、澄清概念:Hack与Workaround

在深入技术细节之前,需要澄清一个重要的概念区分:Hack与Workaround

CSS Hack特指那些利用浏览器bug或私有语法来实现特定效果的技术。例如:

  • IE6的星号选择器:* html .element { ... }
  • 下划线属性:_color: red;(仅IE6识别)
  • 反斜杠转义:color: red\9;(仅IE8及以下识别)

这些技术违反CSS规范,依赖浏览器漏洞,随着浏览器更新随时可能失效。

Workaround(变通方案) 则不同:它是在标准功能不完善或缺失时合法使用现有CSS特性达到目标的方法。浮动布局正是典型的Workaround——我们并没有利用任何浏览器bug,而是在CSS规范允许的范围内,创造性地使用了float这个为文字环绕设计的属性。

所以,请不要说"浮动是hack",应该说:"浮动是特定历史条件下的优雅workaround"。

理解了Workaround的概念后,我们现在可以深入探讨浮动布局这个经典案例:它是如何从简单的文字环绕工具,演变为整个Web布局的基石,又为何最终被更现代的布局方案所取代。

二、margin的水平居中,或许是最常见的workaround

观察浮动布局的那页css,第一个布局出现在body上。

margin:0 auto;

这个看似简单的声明,我认为,是css早期遗留下来的,最大的Workaround。

过去的布局方案,更像是微操。我们都知道,布局应该是从宏观到微观。可,使用盒子模型进行布局,难道不违反逻辑吗?

margin的设计初衷,是为了单个元素定义与相邻元素的 "安全距离"

别忘了,margin实际上是margin-topmargin-rightmargin-bottommargin-left的简写。

margin:0 auto;应该等同于margin-left:auto;margin-right:auto;

直觉告诉我们,如果要水平居中,应该让盒子的左边距等于盒子右边距。

auto,是自动吃掉父元素的剩余空间。

只给左边或者只给右边auto,那么,表现是盒子靠向了另一遍。

如果左右两边都auto,浏览器会认为,左右都要吃剩余空间......怎么办,平均分配呗......

可......这种写法,不管是程序员,还是浏览器,都要猜测这个意图,且auto这种没有脱离正常流的规则,没有办法用于垂直居中。

margin 0 auto的写法,和现代布局里的grid-template-columns:1fr (盒子大小) 1fr,是不是很像?(如果看不懂,没关系,我们在现代版本的页面有详细解释)

grid的写法是显式的,margin的写法是隐式的

跑个题来说,例如text-align:center,不难发现,如果我们希望居中,通常是给他设定对应的规则,而不是先告诉它吃左边,再告诉它吃右边。

现代布局的flexbox和grid都有类似的操作,这一页不做过多赘述。

三、nav导航下的workaround

nav采用的定位规则是position: fixed;。fixed会脱离文档流,内容会被固定在视口的固定位置。

fixed的正常设计是,用top/bottom设置垂直位置,left/right设置水平位置。如果同时设置两个(例如同时写topbottom),那么元素会被拉伸或者压缩,使得满足要求。

看我们的代码:

top: 0; left: 0; right: 0;

理论上,这个元素会填满页面上方的从左到右。

但是,我们设置了width

浏览器发现,width被限制了,没法拉伸,然后还要求左右为0......

这不是欺负浏览器嘛! 那只能按你的要求把内容缩回1200px,按文档流那样从左到右放......

浏览器又发现,margin: 0 auto;......ok,他开始计算剩余距离,然后平均分配给左右......用户一看,说,太好了,居中了......可浏览器该麻了,浏览器和其他程序员会想,这人的代码在嘀咕什么呢......

以上,是对left + right + width的滥用。

2010年之后,诞生了一种新方法: transform属性。

原本,transform是拿来做动画的,做变形的,而非布局。因为,transform实际上压根不会触发布局......

搭配上了绝对定位或固定定位这种脱离文档流的东西......(聪明的你应该已经理解了)

那么,我们用脱离文档流的定位,然后left: 50%;,再用transform: translateX(-50%);......不难发现,这个操作其实是两步,先让元素的左边距等于50%,然后再往左移动自身宽度的50%...

这样的设计很巧妙,但本质上还是通过欺骗浏览器实现的,而且比相对定位更阴。

相对定位会老老实实上报给布局系统,要求浏览器给它留着原来的 "屁股印" ,而transform自己就偷偷跑了,布局系统压根就不知道......

以上,无论是left/right的滥用,还是transform的滥用,都属于workaround。css的设计应当遵循 "继承性" 。现代布局的flex和grid都有对应的办法,类似于text-align: center;

父元素制定规则,子元素遵守规则。这要更加符合css的逻辑,且书写是显式的。不用欺骗浏览器,不用"防御"同行程序员。

四、浮动布局,一个基于workaround搭建的巧妙构思

无论是 margin: auto 的隐式计算,还是 transform 的暗度陈仓,都是对单个元素的"微操"。而进入多元素布局的领域,我们迎来了Workaround的集大成者——float布局。

浮动,本来用于做文字环绕排版。但工程师发现,浮动可以用于水平布局,且兼容性较好......于是,浮动布局时代,开始了。

我们从css的设计角度来看,会发现,float,同样违反了"父元素制定规则,子元素遵守规则"的设计。你会发现,每个子元素,都需要添加float:left(right);实际上想表达的含义很简单:希望元素从左到右(或者从右到左)排列,如果排不下就换行。

flex里,我们只需要在父元素里写两行:首先让父元素变成弹性盒子,然后允许子元素换行。Ok,完成了。

所以,违反设计意图是动机判断,父元素高度塌陷则是直接证据:

浮动会脱离文档流,导致父元素无法被撑开。

我们通常有两种办法:

第一是,在父元素内部的最后一个元素后面,增加一个空元素,为其设置clear:both;而其的完善版本,也就是.clearfix::after,使用了伪元素。这样,它会出现在浮动元素的后面,但由于本身没有宽高,也没有内容撑......所以,它的底边框会紧贴着父元素的底边框。

第二是,为父元素设定overflow:hidden;这触发了bfc。注意,不是kfc(严肃)。bfc,总之就是一个新的布局规则,允许父元素连着浮动子元素的高度一起计算。它类似于我们现代布局的display:flex,或者grid。实际上,css3增加了一个display:flow-root;这允许我们无副作用地清除浮动。但,在当时,css2.1时,只有bfc的概念,而没有一个显式的声明方法。开发者们发现,overflow:hidden可以触发bfc。然而,我们都知道,这个属性会导致超出父元素的内容会被剪切。

很多人可能没听过flow-root属性。我觉得,css3开始,设计哲学开始走向显性的规则声明……我认为这就像0的阶乘等于1一样,是为了完善逻辑链。

你看,总结一下我们的方法。添加空div,违反内容与样式分离;使用浮动,代码行数如此多,一眼就知道是Workaround;至于overflow:hidden,更是带有副作用......

此外,还记得我们一开始讲过的margin:0 auto;吗?这个属性给浮动时,居中无法生效。

理由很简单,浮动会让元素脱离文档流,然后让元素尽可能贴着父元素的left或者right。这就像,你不能给高度设置auto来垂直居中一样,因为,文档流会让元素尽可能贴着父元素的top

五、深入浮动布局:水平居中的困境与解决

尽管浮动如此不方便,但得益于前人的智慧,网页正在变得美观。你现在看到的这个页面,就是浮动时代所能做出来的(不考虑响应式的话)。然而,实现这样一个页面背后,是无数令人头疼的细节。

以导航栏为例,我们遇到了一个棘手的问题:多个导航项需要水平居中排列。如上文所述,不仅margin:0 auto对浮动元素无效,而且浮动本身甚至让我们无法简单地让一个元素居中。

当时的解决方案简单而粗暴:我们先用一个容器(比如一个

    )把所有的导航项(
  • )包裹起来,然后计算这个容器需要多宽才能刚好装下所有浮动元素。接着,我们让这个容器不浮动,并给它设置margin:0 auto;来居中。而容器内的每个导航项则通过浮动来水平排列。

    这个过程看似解决了问题,但仔细思考就会发现,它完全颠倒了正常的布局逻辑:我们不得不先让子元素(导航项)浮动起来,然后根据子元素的总宽度来设定父容器的宽度,最后再让父容器居中。这相当于让子元素决定了父元素的尺寸,而父元素仅仅作为一个被动的居中工具。

    这种反逻辑的做法,在更复杂的布局中会带来灾难。想象一下,如果页面中有多个这样的结构,每个都需要精确计算宽度,那么我们的代码中将充斥着无数个只为包裹浮动元素而存在的容器。这些容器没有实际语义,只为了布局而存在。这不仅导致HTML结构冗余,可读性下降,而且一旦内容发生变化,所有相关的宽度都需要重新计算,维护成本极高。

    因此,浮动布局不仅在微观上(如高度塌陷)需要各种修补,在宏观的布局结构上也迫使开发者写出违背语义化、结构臃肿的代码。这进一步证明了,浮动作为一种布局工具,是一个庞大而脆弱的Workaround体系。

    六、初入圣杯布局

    浮动下的圣杯布局的是什么和怎么做,已经有不少前人提过(此外我的Gitee仓库里也提供了我亲手做的网页!可以查看我的代码,注释很详细!QwQ)。你肯定已经知道了padding预留侧边栏,设置负边距拉回上一行,以及相对定位进一步微调的使用方法。

    这里,我认为分析这一系列操作为什么是Workaround,其实没有意义,因为,你第一眼看代码的时候,就已经疑惑了。

    当每个代码的单个操作你都能理解,合起来就看不懂的时候,大概率说明,这是Workaround。

    但为了让这文章看起来说服力更强,我们来细数他的罪恶。我们会组合地去看,因为,这些单个属性的设置,除了看起来有病以外,并不能算Workaround。

    首先,利用盒子模型进行布局,巨大的padding虽然给了父元素,但,我们自然而然地会认为,内边距只显示背景,不显示内容。这里,预留的内边距却被浮动覆盖在了上面。怎么做到的呢?

    通常来说,我们设置负数的margin,是为了重叠没错。可,当元素是块元素,甚至行内元素时,负数的margin不能触发换行。神奇的是,搭配浮动后,他就能被拉回上一行了(请注意,看起来类似的行内块却不行)。

    于是,浮动上来的左右侧边栏,至少是同一行了。但不精准,尤其是右侧边栏:拉上来后,我们发现,它在预留区域的左侧——相当于,你还需要重新往回拉。

    怎么办?相对定位出场了。相对定位本意是被拿来微调元素的,因为,他会留着元素原本所处的,在文档流的位置......搞笑的来了:浮动让元素脱离了文档流。

    你可能比较聪明,想到了transform。为什么我们用相对定位这种局限更强的办法呢?实际上,transformtranslateX属性,完全可以替代相对定位。为什么提到浮动圣杯布局,大家却选择了更弱的版本呢?这其实和前文提到的display:flow-root;一样——诞生的时间太晚了。好在,transform根本目的不是用来布局的,他没有被冷落。

    如果你比较细心,你会发现,我的页面,左中右高度一样。怎么做到的呢?中间是内容撑开,那左右呢?

    在浮动布局中,这会很棘手。我们通过设置了巨大的padding和负数margin这种"魔法数字"来实现了伪等高。这里,我们利用了overflow:hidden会裁剪多余内容的特性。(这里和bfc无关了。虽然bfc碰巧解决了浮动问题,以至于我们的父盒子不需要设置.clearfix类了,但依然存在潜在的裁剪内容的问题。)

    在现代布局中,我们通常会默认把父元素的直系子元素拉伸到等高。因此,我们不再因为这个问题而感到困扰了。

    总结来看,整个圣杯布局,就像一个全是bug,但跑起来了的程序——倘若你想修复一个bug,例如,你希望让它的响应式更强一些......除非一个像素一个像素地媒体查询,否则......不现实。

    七、盒子模型

    我们前文提过的margin居中办法,就属于盒子模型。之所以放到最后面讨论,是因为,我相信作为读者的你,已经对盒子模型的滥用这件事感到荒谬了。

    因此,这里我们就放松一下,单独谈一下margin的塌陷问题吧。外边距塌陷最初来自于印刷排版,用于保持段落间距的一致性。在印刷中,我们通常希望连续的段落之间有一个固定的间距,而不是两个段落间距的累加。

    想象一下word。你的标题外边距是40px,你的段落外边距是12px。你肯定不希望他们叠加起来。同样的,段落外边距是12px,段落与段落之间,你也不希望他们叠加起来。因此margin的塌陷并不是bug,相反,这正好说明,盒子模型压根就没被想到要拿来布局用!

    根据CSS规范,外边距塌陷发生在两个相邻的块级元素之间,并且它们之间没有分隔物。边框border和内边距padding被视为分隔物,因此会阻止外边距合并。

    border: 1px solid transparent;因此出现,意思是,这个元素需要1px厚度的透明实线宽度。

    与其讨论设计边框是否为Workaround,我认为,在默认的文档流布局,本身就是巨大的错误!

    父元素制定规则,子元素遵守规则。这是我们通篇在强调的理念。塌陷本身是为了照顾印刷排版,而这里,你不是拿来印刷排版......是否意味着,你应该重新声明规则?没错了。这就是更现代的方案。无论是flex还是grid,亦或者bfc那个flow-root,都能阻止margin塌陷。

    八、总结

    我们通篇强调的父元素制定规则,子元素遵守规则,就是现代布局的解决方法。了解了浮动的那些Workaround,就能知道过去的开发者有多痛苦。让我们感谢那些开拓这些方法的开发者!致敬!


    (本人是一个大一学生,学习前端的时间并不长……以上是我的一些思考,我其实学习前端的程度还不算深刻,但我实践着写了浮动和弹性盒子下的圣杯布局,有了不小的体会……)

    我上传到了gitee上,欢迎下载来看!(注释很丰富的,内心戏强烈QwQ)gitee.com/jeyi08/floa…