Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zwwill opened this issue Nov 13, 2017 · 5 comments
Open

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

zwwill opened this issue Nov 13, 2017 · 5 comments

Comments

@zwwill
Copy link
Owner

zwwill commented Nov 13, 2017

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

@zwwill zwwill added this to the Y-2017 milestone Nov 13, 2017
@zwwill zwwill changed the title 讲讲为什么淘宝要提出「双飞翼」布局 聊聊为什么淘宝要提出「双飞翼」布局 Nov 13, 2017
@zwwill zwwill changed the title 聊聊为什么淘宝要提出「双飞翼」布局 【布局】聊聊为什么淘宝要提出「双飞翼」布局 Dec 1, 2017
@yangbinfx
Copy link

mark.学到了

@znnnnn
Copy link

znnnnn commented Apr 22, 2019

kram

@qqqqqqian
Copy link

学习了

@yuleiQ
Copy link

yuleiQ commented May 7, 2020

学习了

@arkusa
Copy link

arkusa commented Sep 13, 2020

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上去

望斧正

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants