布局方式(下) - 方案与实现

# 前言

  布局方式是每个项目都必须使用到的,大部分新手看到设计稿的第一反应是: 从左到右,从上到下依次制作。对于每个模块,先写个容器,有文字放文字,有图片放图片,外部用paddingmargin拉开距离,有边框加边框,特殊需求加 floatposition属性,然后缺什么补什么 。你是不是也这样做的?除了实现细节上不易于理解外,还缺少了个很重要的步骤:对布局方式的思考。这里总结了几个常见布局方式实现方式,看官如果有不同的想法建议,欢迎到评论区交流。

布局方式

# 设计模式一: 两栏布局

  两栏布局是最常见的移动端布局方式之一,是必须要掌握和深入理解的技能。
描述】页面呈现左右两列分布,左边宽度固定,右边宽度自适应;左右高度互不影响,一般显示左边为icon,右边为文字描述,当文字高度大于icon高度时,文字不能换行到图片下方。
基本样式】左侧盒子款宽度120px, 右侧盒子距离左侧20px,右侧盒子宽度自适应

  <div class="left">
     左边宽度固定120px,高度不固定<br/><br/>高度有可能很小,也可能很大。
  </div>
  <div class="right">
     右边宽度自适应,高度不固定,距离左侧20px.
  </div>
</div>
.wrap {
  padding: 15px 20px;
  border: dashed 1px #3dee70;
}
.left {
  width: 120px;
  border: solid 2px lightblue;
}
.right {
  margin-left: 20px;
  border: solid 2px lightblue;
}

现在只能实现以下效果:(盒子2宽度一直延展至右侧)


说明: 以下样式均在以上的基础上添加

> 双 inline-block 实现方案

.wrap {
  box-sizing: content-box;
  font-size: 0;
}
.wrap .left {
  font-size: 14px;
  display: inline-block;
  vertical-align: top;
  box-sizing: border-box;
}
.wrap .right {
  width: calc(100% - 140px);
  font-size: 14px;
  display: inline-block;
  vertical-align: top;
  box-sizing: border-box;
}

解释】通过width: calc(100% - 140px)来动态计算右侧盒子的宽度。设置左右两个元素都为inline-block 让元素在同一行显示,于是得到如下效果

注释】(1)box-sizing属性允许你以特定的方式定义匹配某个区域的特定元素,可选值有
content-box: 设定的宽度和高度是内容区域占据的空间大小
border-box: 设定的宽度和高度是内容区域加边框区域占据的空间大小
inherit: 继承父元素的box-sizing值

(2)【CSS3】calc() = calc(四则运算) 用于动态计算长度值。需要注意的是,运算符前后都需要保留一个空格,例如:width: calc(100% - 10px);任何长度值都可以使用calc()函数进行计算;

缺点】(1)需要知道左盒子的宽度,两盒值之间的距离,还要设置box-sizing,才能计算出右边盒子宽度
(2)必须要消除空格的影响,即设置父元素为font-size:0;否则依旧会换行。
(3)需要设置vertical-align:top才能顶部居中,否则将展示如下

没有设置左右对齐方式

> 双 float 方案

.wrap {
  overflow: auto;   /* 清除浮动 */
  box-sizing: content-box;
}
.left, .right {
  float: left;
  box-sizing: border-box;
}
.wrap .right {
  width: calc(100% - 140px);
}

解释】同双inline-block思路一致,动态计算右盒子的宽度来实现自适应,比它少的一点是:float的块元素在有空间的情况下会依次紧贴,并排在一行,也就无需设置display: inline-block和设置顶端对齐,空格占位等问题。实现结果如下


引伸】关于浮动布局的官网解释如下:

A floated box is shifted to the left or right until its outer edge touches the containing block edge or the outer edge of another float.

缺点】(1)由于应用了浮动,因此需要清除浮动,此例用父元素设置overflow: auto 是比较简单自然的一种方式
(2)需要知道左盒子的宽度,两盒子的距离,设置各个元素的box-sizing

> float + margin-left 方案

  这或许是被用作最多的方式了,因为它真的很简单,实现如下:

.wrap {
  overflow: auto;    /* 清除浮动 */
}
.left {
  float: left;
  box-sizing: border-box;
}
.right {
  margin-left: 140px;
}

分析】我们知道,block级别的元素会认为浮动的元素不存,但是inline 能正确识别浮动元素,因此设置inline-block可以使 blockfloat 共存。因为block级别的元素盒子的宽度具有填满父容器,并随着父容器的宽度自适应的流动特性。,我们便不需要再计算右盒子的宽度。
缺点】(1)应用了浮动,需要清除浮动
(2)需要计算右盒子的margin-left

> absolute + margin-left 方法 [不推荐]

  让两个block排列在同一行除了float 之外,另一种办法就是利用position: absolute ,如下:

.wrap {
  position: relative;
/*   min-height: 150px; */
}
.left {
  position: absolute;
  box-sizing: border-box;
}
.right {
  margin-left: 140px;
}

缺点】这里没有使用浮动,因此不需要清除浮动,但
(1)设置子元素设置absolute后,也需要给父元素增加position;
(2)同样需要计算右盒子的 margin-left
(3)致命缺陷是,子元素设置了absolute的父元素同样会发生高度崩塌的现象,但有没有类似清除浮动的方法,因此只能如上设置合适的min-height,否则如果左盒子高度大于右盒子,结果将显示如下:

没有设置min-height显示结果

> float + BFC 方法 - [难点]

  细心的人会发现上面的方法都需要根据左盒子去计算得到某个值(widthmargin-left),而下方的方法,只需要知道两个盒子间距是多少。

.wrap {
  overflow: auto;
}
.left {
  float: left;
  margin-right: 20px;
}
.right {
  margin-left: 0; /* 这里的是为了覆盖基础样式中的margin-left: 20px */
  overflow: auto;
}

分析】左盒子左浮动,右侧盒子通过overflow: auto 形成新的 BFC,因此右侧的盒子不会和左侧的发生重叠,看一个官网的解释:

an element in the normal flow that establishes a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the margin box of any floats in the same block formatting context as the element itself。

意思就是: 在普通文档流中的元素新建了一个BFC(如一个带有overflow属性且值不为visible 的元素)不能与原BFC中任何浮动元素本身及边距框发生重叠。
所以,只需要为左侧的浮动盒子设置margin-right 就能实现两盒子的间距,而右边的盒子是block 级别的,能实现宽度自适应。
缺点】(1)父元素需要清除浮动

扩展:BFC
BFC[块级格式化上下文],它是一个环境,HTML元素在这个环境中按照CSS规则进行布局。每个环境中的元素不会影响到其他环境的布局。
如何生成一个新的BFC
(1)浮动元素
(2)绝对定位
(3)块级元素即块级容器(如inline-block、table-cell、table-capation
(4)overflow值不为visible的块级盒子
BFC的执行规则是:(1)在一个块级排版上下文中,盒子是从包含块顶部开始,垂直的一个接一个的排列的。每个盒子的左外边是触碰到包含块的左边的(对于从右向左的排版,则相反)
(2)相邻两个盒子之间的垂直的间距是被margin属性所决定的,在一个块级排版上下文中相邻的两个块级盒之间的垂直margin是折叠的。
(3)BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。

> flex 方案 - [推荐]

.wrap {
  display: flex;
  align-items: flex-start;   /* 默认为stretch,即: 列等高 */ 
}
.left {
  flex: 0 0 auto;
}
.right {
  flex: 1 1 auto;
}

分析】左盒子大小用flex: 0 0 auto 固定,右盒子大小用flex: 1 1 auto自动缩放。
注释】flex 是 flex-grow、flex-shink、 flex-basis的简写,flex-grow: 0表示空间大于所需空间时保持不变,flex-grow: 1 表示空间大于所需空间时放大到父元素空间大小。同理类推flex-shinkflex-basis是元素基础大小,auto 表示原来子元素多大,基础大小就多大。具体内容见我的另一片文章 布局方式


# 设计模式二: 三栏布局

  两栏布局主要应用在移动端,三栏布局的市场则在PC端,想做PC端的同学,不会三栏布局可不行。
描述】三栏布局是指两边固定,中间自适应的一种布局方式,三栏布局非常常见,如 某东的首页,左右两边一般为固定宽度的导航栏,中间为信息展示栏,宽度随着浏览器宽度自适应。
基本样式】适配最小的pc屏幕,屏幕大于所需大小时两边留白。距离左边大小为 (100%-1366px)/2

> 双 float + 主体 margin

  该方案的思路是:左盒子设置左浮动,右盒子设置右浮动,中间盒子利用左右margin拉开距离

.wrap {
    max-width: 1366px;
    margin-left: calc(50% - 683px);
    border: dashed 2px red;
    overflow: auto;    /* 清除浮动 */
    box-sizing: content-box;
}
.left {
    float: left;
    width: 250px;
    height: 150px;
    border: solid 2px lightblue;
    box-sizing: border-box;
}
.right {
    float: right;
    width: 200px;
    height: 150px;
    border: solid 2px gainsboro;
    box-sizing: border-box;
}
.main {
    margin: 0 220px 0 270px;
    height: 100px;
    border: solid 2px lightgreen;
    box-sizing: border-box;
}
<div class="wrap">
   <div class="left">左盒子左浮动,宽度250px; 一般用来显示商品列表</div>
   <div class="right">右盒子设置右浮动,宽度200px;一般用来显示作者信息或广告信息等</div>
   <div class="main">中间盒子展示主体内容,宽度随浏览器宽度自适应</div>
</div>

分析】左右盒子浮动,所以要把左右盒子写在前面,主体盒子写在后面,否则主体盒子是block级别单独占用一行,导致右盒子显示在下一行。以上代码最终结果如下:

缺点】主体内容在左右盒子下方,无法优先加载,当页面内容较多时影响用户体验;需要清除浮动,否则高度只有主体内容高度

> float + BFC 方法

  在上文中的两栏布局可知:BFC区域,不会与浮动元素发生重叠;那么我们可以如此实现:

.left {
    float: left;
    width: 250px;
    height: 150px;
    margin-right: 20px;
    border: solid 2px lightblue;
    box-sizing: border-box;
}
.right {
    float: right;
    width: 200px;
    height: 150px;
    margin-left: 20px;
    border: solid 2px gainsboro;
    box-sizing: border-box;
}
.main {
    height: 100px; 
    overflow: auto;  /* 生成一个新的 BFC */
    border: solid 2px lightgreen;
    box-sizing: border-box;
}
<div class="wrap">
   <div class="left">左盒子左浮动,宽度250px; 一般用来显示商品列表</div>
   <div class="right">右盒子设置右浮动,宽度200px;一般用来显示作者信息或广告信息等</div>
   <div class="main">中间盒子展示主体内容,宽度随浏览器宽度自适应</div>
</div>

评论】与上方案一致,主体无法优先加载,父元素需要清除浮动。效果与上例一致。

以上两种方案都无法优先加载主体内容,下方的方案都可以实现这点。

> 双飞翼布局 : float + 负边距(margin) 方法

  既然左右盒子都能使用浮动来显示在预期位置,那为什么主体盒子就不能呢?当然是可以的。如下所示:

<div class="wrap">
    <div class="box">
        <div class="main">主体内容被包含在一个浮动的盒子中,在DOM树中该盒子放置在左右盒子上方</div> 
    </div>
    <div class="left">‘左盒子’宽度固定250px; 设置负margin为100%</div>
    <div class="right">‘右盒子’宽度固定200px; 设置负margin为200px</div>
</div>

HTML 结构如上改变之后,再看看 CSS 的变化

.wrap {
    max-width: 1366px;
    margin-left: calc(50% - 683px);
    border: dashed 2px red;
    overflow: auto;      /* 清除浮动 */
    box-sizing: content-box;
}
.box {
    float: left;
    width: 100%;
    border: dashed 2px blue;
    box-sizing: border-box;
}
.main {
    height: 150px;
    margin: 0 220px 0 270px;
    border: solid 2px gainsboro;
    box-sizing: border-box;
}
.left {
    float: left;
    width: 250px;
    height: 150px;
    margin-left: -100%;
    border: solid 2px lightblue;
    box-sizing: border-box;
}
.right {
    float: right;
    width: 200px;
    height: 150px;
    margin-left: -200px;
    border: solid 2px lightgreen;
    box-sizing: border-box;
}

【分析】对承载 main 的盒子也设置浮动,就有了 wrap 的第一级子元素 box-left-right 都是浮动元素。因为浮动元素会创建 BFC 而 BFC 不会与另一个BFC发生重叠的现象,就能得到 box-left-right 元素互斥的效果。这时候效果如下:

去除left 和 right 的负边距值后的显示效果

那为什么100%宽度的 box 又和 left盒子right盒子 显示在一行了呢?
  因为我们设置了负边距。BFC 本身不会发生重叠,我们却可以手动的用负margin 来实现重叠,‘左盒子’设置负margin为100%,即移动到最左侧,‘右盒子’设置 负margin 为200px即本身宽度的100%,可以显示在最右侧,所以,可以得到如下效果:

评价】解决了主体内容不会优先加载的问题,复杂了HTML。

> 圣杯布局

  跟双飞翼布局很像,圣杯布局统一了HTML结构,但复杂了样式。

.wrap {
    max-width: 1366px;
    margin-left: calc(50% - 683px);
    border: dashed 2px red;
    overflow: auto;    /* 清除浮动 */
}
.box {
    margin: 0 220px 0 270px;
    border: solid 2px blue;
    box-sizing: content-box;
}
.main {
    float: left;
    width: 100%;
    height: 150px;
    border: solid 2px gainsboro;
    box-sizing: border-box;
}
.left {
    float: left;
    width: 250px;
    height: 150px;
    margin-left: -100%;
    position: relative;
    left: -270px;
    border: solid 2px lightblue;
    box-sizing: border-box;
}
.right {
    float: left;
    width: 200px;
    height: 150px;
    margin-left: -200px;
    position: relative;
    right: -220px;
    border: solid 2px lightgreen;
    box-sizing: border-box;
}
<div class="wrap">
    <div class="box">
        <div class="main">主体内容,红框位置即box真实大小</div> 
        <div class="left">‘左盒子’宽度固定250px; 设置left为100%,移动到最左侧</div>
        <div class="right">‘右盒子’宽度固定200px; 设置right为-220px,即本身宽度加间隔距离</div>
    </div>
</div>

思路和双飞翼布局基本相同,效果如下:


box发生高度塌陷

缺点】box发生高度塌陷且无法清除浮动,给后期维护埋有隐患。

> flex布局方案 - 推荐

  毕竟是未来的趋势,来看看它又是怎么实现的:

.wrap {
    max-width: 1366px;
    margin-left: calc(50% - 683px);
    display: flex;
    border: dashed 2px red;
}
.main {
    flex: auto;
    height: 100px;
    border: solid 2px aqua;
}
.left {
    order: -1;
    flex: 0 1 250px;
    height: 180px;
    margin-right: 20px;
    border: solid 2px lightblue;
}
.right {
    order: 1;
    flex: 0 1 200px;
    height: 150px;
    margin-left: 20px;
    border: solid 2px lightgreen;
}
<div class="wrap">
    <div class="main">主体内容,设置flex:auto 即自动缩放</div> 
    <div class="left">‘左盒子’宽度初始大小250px; 设置flex空间大了不变,空间小了等比缩小</div>
    <div class="right">‘右盒子’宽度初始大小200px; 设置flex空间大了不变,小了等比缩小</div>
</div>

【分析】是不是感觉很清爽~; 设置父元素为flex 布局,用 flex-grow, flex-shrink, flex-basis 的简写 flex指定哪个元素固定大小,哪个元素自适应,固定大小的初始大小为多少,各个元素的排列方式是什么样的。思路简单明确。结果如下:

评价】代码量少,主体内容能够优先加载,但需要考虑浏览器的兼容性,优先推荐。

> absolute + margin 方案

  比较简单的一种实现方式,没什么设计思路,大致如下:

.wrap {
    max-width: 1366px;
    margin-left: calc(50% - 683px);
    position: relative;
}
.main {
    height: 150px;
    margin: 0 220px 0 270px;
    background: gainsboro;
}
.left {
    position: absolute;
    width: 250px;
    height: 150px;
    top: 0;
    left: 0;
    background: lightblue;
}
.right {
    position: absolute;
    width: 200px;
    height: 150px;
    top: 0;
    right: 0;
    background: lightgreen;
}
<div class="wrap">
    <div class="main">主体内容,在position为relative的父元素下,直接设置左右拉开距离</div> 
    <div class="left">‘左盒子’宽度初大小250px; 设置absolute且left为 0</div>
    <div class="right">‘右盒子’宽度大小200px; 设置absolute且right为 0</div>
</div>

分析】同两栏布局设计一样,如果主体内容的高度小于左右盒子高度,wrap 的高度子有主体内容高度大小。如下所示

三栏布局还有其他的实现方案如风靡在“远古时期”的表格布局等,个人觉得不太实用不再总结,有兴趣的朋友可以自己了解一下。


# 设计模式三: 多栏等宽布局

  以上总结的 两栏布局三栏布局 主要应用于 移动端和PC端的整体结构设计,而 多栏等宽布局 则多用在 商品销售的展示页面上。如 某宝,某东,某当等。
  这里主要介绍一下四栏等宽自适应布局

> flex 方案

.wrap {
    max-width: 800px;
    margin-left: calc(50% - 400px);
    border: dashed 2px red;
    display: flex;
    justify-content: space-between;
    box-sizing: content-box;
}
.wrap div {
    width: calc(100%/4);
    height: 300px;
    float: left;
    flex: 0 1 auto;
    border: solid 2px lightblue;
    box-sizing: border-box;
}
.wrap div + div {
    margin-left: 20px;
    margin-bottom: 20px;
}
<div class="wrap">
   <div>商品展示区</div>
   <div>商品展示区</div>
   <div>商品展示区</div>
   <div>商品展示区</div>
</div>
<div class="wrap">
   <div>商品展示区</div>
   <div>商品展示区</div>
   <div>商品展示区</div>
   <div>商品展示区</div>
</div>

实现效果如下:


# 后语

  本文总结了一些常用的做法,对比了各方案的优缺点和可实现性,如果有疏忽和不对的地方,欢迎大家评论区交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,322评论 5 465
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,288评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,227评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,015评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,936评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,534评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,995评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,616评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,907评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,923评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,741评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,525评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,016评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,141评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,453评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,054评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,249评论 2 339