zwwill/blog

【布局】聊聊为什么淘宝要提出「双飞翼」布局

zwwill opened this issue · 5 comments

image

前言

突然有一天,脑之里不知怎地蹦出一个词,「双飞翼」,这是很久以前的淘宝提出的一种三栏布局优化方案,然而,时间久了已经不记得(换句话说是不理解)为啥要提出这个布局了,昨天在 SF 上发起了一个提问,但良久未有人答复,幸得@王能全是谁 提醒,终于回想起「双飞翼」的完整意义了。谨以此文同大家分享这段心路历程。

圣杯 & 双飞翼

说到「双飞翼」就不得不提及「圣杯」,两者均为三栏布局的优化解决方案如下图

常规情况下,我们的布局框架使用以下写法,从上到下,从左到右。

<header>header</header>
<section>
    <aside>left</aside>
    <section>main</section>
    <aside>right</aside>
</section>
<footer>footer</footer>

问题倒是没什么问题,然而,如果我们希望中部 main 部分优先显示的话,是可以做布局优化的。

因为浏览器渲染引擎在构建和渲染渲染树是异步的(谁先构建好谁先显示),那么将<section>main</section>部分提前即可优先渲染。

<header>header</header>
<section>
    <section>main</section>
    <aside>left</aside>
    <aside>right</aside>
</section>
<footer>footer</footer>

于是乎,国外的前辈就提出了「圣杯」布局,目的就是通过 css 的方式配合上面的 DOM 结构,优化 DOM 渲染。

我们来简要地了解一下「圣杯」布局,这不是重点。

圣杯布局

demo :https://codepen.io/zwwill/pen/OBYXEa

<template>
<header>header</header>
<section class="wrapper">
    <section class="col main">main</section>
    <aside class="col left">left</aside>
    <aside class="col right">right</aside>
</section>
<footer>footer</footer>
</template>

<style>
/* 以下为简码,仅保留关键部分 */
header,footer {height: 50px;}
.wrapper {padding: 0 100px 0 100px; overflow:hidden;}
.col {position: relative; float: left;}
.main {width: 100%;height: 200px;}
.left {width: 100px; height: 200px; margin-left: -100%;left: -100px;}
.right {width: 100px; height: 200px; margin-left: -100px; right: -100px;}
</style>

使用了 relative 相对定位float(需要请浮动,此处使用 overflow:hidden; 方法)和 负值 margin ,将 left 和 right 部分「安装」到 wrapper 的两侧,顾名「圣杯」。具体的思路我就不再做赘述了,网上到处都是解释。

圣杯有问题

当然,正常情况下是没有问题的,但是特殊情况下就会暴露此方案的弊端,如果将浏览器无线变窄,「圣杯」将会「破碎」掉。如图,当 main 部分的宽小于 left 部分时就会发生布局混乱。

于是,淘宝软对针对「圣杯」的缺点做了优化,并提出「双飞翼」布局。

双飞翼布局

demo :https://codepen.io/zwwill/pen/oaRLao

同样的我们来看简码

<template>
<header>header</header>
<section class="wrapper">
    <section class="col main">
        <section class="main-wrap">main</section>
    </section>
    <aside class="col left">left</aside>
    <aside class="col right">right</aside>
</section>
<footer>footer</footer>
</template>

<style>
/* 以下为简码,仅保留关键部分 */
header,footer {height: 50px;}
.wrapper {padding: 0; overflow:hidden;}
.col {float: left;}
.main {width: 100%;}
.main-wrap {margin: 0 100px 0 100px;height: 200px;}
.left {width: 100px; height: 200px; margin-left: -100%;}
.right {width: 100px; height: 200px; margin-left: -100px;}
</style>

同样使用了 float负值 margin,不同的是,并没有使用 relative 相对定位 而是增加了 dom 结构,增加了一个层级。确实解决了圣杯布局的缺陷。

为什么要设计「双飞翼」布局

双飞翼布局表面上看是很优秀,但是细细想来,为什么要多加一层 dom 树节点,这岂不是增加了 css 样式规则表和 dom 树合并成布局树的计算量吗?

好像绝对定位也可以解决这个问题

细想想,我们可以使用绝对布局,将左右侧边栏定位到到两侧啊?好像也不会出现圣杯布局的毛病?

<template>
<header>header</header>
<section class="wrapper">
    <section class="col main">main</section>
    <aside class="col left">left</aside>
    <aside class="col right">right</aside>
</section>
<footer>footer</footer>
</template>

<style>
/* 以下为简码,仅保留关键部分 */
header,footer { height: 50px;}
.wrapper { position: relative;}
.main { height: 200px; margin:0 100px;}
.left, .right{ width: 100px; height: 200px; position: absolute; top: 0;}
.left{ left: 0;}
.right{ right: 0;}
</style>

没有使用 float(不用请浮动)也没有 负值 margin ,仅仅使用了 absolute 绝对定位,好像更优秀呢?

但是细细想想,单纯的绝对定位有一个问题,「高度不可控」,我们假设,如果 left 部分的高度高于 main ,是不是 left 没有能力撑起整个 wrapper

「四不四」~~!

那么我们再来看看双飞翼和圣杯的情况

都是下图。

「应戳死听」~~!

那这么看来,所有的方案都或多或少存在一些问题。综合来看,不管 left, main, right 的大小高低如何,「双飞翼」布局都能正常显示,嗯~~确实很优秀。

锤子和钉子

综上所见,「双飞翼」布局更胜一筹。但是,这是一个「锤子和钉子」的问题,我们应该拿着钉子找锤子,而不是拿着锤子找钉子,因为,当你有了最大的锤子,看到什么都是钉子。

唉~,我又在装逼了。 \( ̄︶ ̄)/

说白了,就是,对症下药,没有最好的方案,只有最适合的。关于三栏布局,我帮大家列出一个对照表,以便大家快速选择。

- 优点 缺点
圣杯 结构简单,无多余 dom 层 中间部分宽度小于左侧时布局混乱
绝对定位 结构简单,且无需清理浮动 两侧高度无法支撑总高度
双飞翼 支持各种宽高变化,通用性强 dom 结构多余层,增加渲染树生成的计算量

以上为个人理解,如有不对或可补充之处,还请指点。

另外关于 CSS 布局方案,和前端性能优化部分,移驾一下文章
多行多列类布局方案总结
前端性能优化总结

转载请标明出处
作者:木羽 zwwill
首发地址:#11

mark.学到了

kram

学习了

学习了

HI,
umm... 我想可能双飞翼布局是对圣杯布局的一种hack优化

可以试一下将left元素margin-left的值和left值对调下, 可能会发生一些神奇的事情(即使main.width < left.width, 页面依旧正常)

其原因是(下面是出自我的理解, 暂时没有找到文档证明)(下面我假设left.width = 100px; main.width = 200px; right.width = 50px)

首先我想提出几个概念

  1. **margin-left: -100px; 相当于这个元素的宽度减少了100px; **

  2. 浏览器在渲染inline性质的盒子优先在其前一个兄弟节点(这个兄弟节点也是inline性质的盒子)后渲染

    如果剩余的空间能够渲染下,就在当前行渲染
    否则换行渲染

  3. 如果一个元素的宽度是100,但是其margin-left: -200px(不考虑其他padding, border...), 那么在浏览器计算这个元素的宽度的时候会得到-100px

    这是为什么圣杯布局的左右2个部分能够渲染到main行的根本原因

    当前行刚好被充满, 剩余空间为0. 此时计算得到的元素宽度是-100px < 0, 那么这个元素会在当前行绘制, 而且会相距包含块的右边100px

    我看有人说和浮动有关, 这是错误的🙅‍♂️ , 可以用inline-block测试下, 也是可以的

综上可以得出为什么圣杯布局在left.width < main.width会异常?

我们知道margin-left: -100%; 这里的-100%是相对于包含块计算的,在圣杯布局下contain_block.width = main.width

于是有margin-left = -main.width, 浏览器认为的元素宽度为left.width + margin-left > 0 所以在当前行不能绘制left, 所以就掉下来了,当然right 也跟着掉下来了


但是如果我们设置margin-left: -100px; -100px是left.width得到的, 那么浏览器计算的元素的宽度是0, 和剩余空间是相等的,能够被渲染...


而双飞翼布局是依据, 既然left.width < main.width会异常, 那就让main.width 恒>= left.width ,并没有从根本上(即:为什么圣杯布局会异常)来优化圣杯布局


comment旨在解释

  1. 圣杯布局异常的原因

  2. 为什么left和right会跑到main上去

望斧正