# 前言
布局方式是每个项目都必须使用到的,大部分新手看到设计稿的第一反应是: 从左到右,从上到下依次制作。对于每个模块,先写个容器,有文字放文字,有图片放图片,外部用padding
、margin
拉开距离,有边框加边框,特殊需求加 float
、position
属性,然后缺什么补什么 。你是不是也这样做的?除了实现细节上不易于理解外,还缺少了个很重要的步骤:对布局方式的思考。这里总结了几个常见布局方式实现方式,看官如果有不同的想法建议,欢迎到评论区交流。
# 设计模式一: 两栏布局
两栏布局是最常见的移动端布局方式之一,是必须要掌握和深入理解的技能。
【描述】页面呈现左右两列分布,左边宽度固定,右边宽度自适应;左右高度互不影响,一般显示左边为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
可以使 block
和 float
共存。因为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
,否则如果左盒子高度大于右盒子,结果将显示如下:
> float + BFC 方法 - [难点]
细心的人会发现上面的方法都需要根据左盒子去计算得到某个值(width
或 margin-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-shink
。flex-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
元素互斥的效果。这时候效果如下:
那为什么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发生高度塌陷且无法清除浮动,给后期维护埋有隐患。
> 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>
实现效果如下:
# 后语
本文总结了一些常用的做法,对比了各方案的优缺点和可实现性,如果有疏忽和不对的地方,欢迎大家评论区交流。