跳至主要內容

布局和位置

xmut-lby大约 10 分钟

布局和位置

到目前为止,以我们学过的知识,只能排列一条线,显然是无法满足我们正常的需要的。

要实现变化多端的页面效果,我们离不开布局。

一般旧式教程会从float效果开始讲起,但目前现代浏览器会更建议使用flex和grid布局。

flex布局是一维的(线状),grid则是二维(平面,有x、y),目前大多数网站还是倾向于使用兼容性更好的flex布局。

flex布局

flex布局的标记存在容器(父标记)和元素(子标记)的区别。这很容易理解:

容器(默认情况下)横向方向称为主轴,与主轴垂直的轴称为交叉轴。元素会沿主轴逐一排列。

flex的容器和元素
flex的容器和元素

这里引用一下MDNopen in new window

MDN关于flex的说明

文档中采用了 flexbox 的区域就叫做 flex 容器。为了创建 flex 容器,我们把一个容器的 display 属性值改为 flex 或者 inline-flex。 完成这一步之后,容器中的直系子元素就会变为 flex 元素。 由于所有 CSS 属性都会有一个初始值,所以 flex 容器中的所有 flex 元素都会有下列行为:

  • 元素排列为一行(flex-direction 属性的初始值是 row)。
  • 元素从主轴的起始线开始。
  • 元素不会在主维度方向拉伸,但是可以缩小。
  • 元素被拉伸来填充交叉轴大小。
  • flex-basis 属性为 auto。
  • flex-wrap 属性为 nowrap。

涉及的属性

这里我们先列出所有涉及flex布局的属性,方便后面对照:

.container{ /* 用于容器(父标记)的属性 */
    display: flex; /* 说明是个flex容器 */
    flex-direction: ; /* 元素如何排列 */
    flex-wrap: ; /* 元素是否允许换行 */
    justify-content: ;/* 元素主轴上的对齐方式 */
    align-items: ;/* 元素交叉轴上的对齐方式 */
}

.item{ /* 用于元素(子标记)的属性 */
    flex-grow: ;/* 容器扩大时的增长规则 */
    flex-shrink: ;/* 容器缩小时的收缩规则 */
    flex-basis: ;/* 元素的尺寸,类似width*/
    flex: ;    /* 以上三者的简写法 */
    align-self: ; /* 元素在交叉轴(竖直方向)上的对齐方式  */
    order: ;
}

推荐的教程

这里列出B站比较推荐的关于flex的教程,突出一个2分钟掌握,作为概括性的了解比较适合:

五分钟掌握 css3 flex弹性布局 超详细!open in new window

2分钟掌握 CSS flexbox 布局open in new window

flex弹性布局 动画详解系列 css科普教程open in new window

还是要强调一下,不动手看了也没意义。

实战一下

我们假设要做这么一个效果,分为左右两栏,左边500px,右边是1000px,居中,中间用空白分开:

最终效果
最终效果

原始状态

以下内容请在编辑器里尝试一下,否则你记不住,注意!

现在假设有以下的两个块,我们希望进行分栏:

    <div class="leftbar">
        <h1>左边栏</h1>
    </div>
    <div class="rightbar">
        <h1>右边栏</h1>
        <p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Vitae sit nihil numquam? Unde sed quam dolorum nisi? Repudiandae ipsum alias soluta molestiae! Iste ea saepe sapiente facere nemo eos natus?</p>
    </div>

我们简单设置一下css,让页面不同的部分更醒目一些:

h1{
    font-size: 3em;
}

div{
    border: 2px solid black;
    padding: 20px;
}

.rightbar>p{
    font-size: 150%;
}

.leftbar{
    margin-right: 20px;
}

.rightbar{
    background-color: coral;
}

现在看看效果:

最初的效果
最初的效果

转为flex显示模式

第一步我们首先需要一个flex容器,然后把两个分栏放入容器中,并设置容器为display:flex

<div class="container">
    <div class="leftbar"> ... </div>
    <div class="rightbar"> ... </div>
<div>
.container{
    display: flex;
}

这时你已经可以看到形成双栏了:

成功双栏
成功双栏

元素块的属性

不过别急,你可以拉动窗口,两个分栏的大小会随着窗口尺寸(父节点尺寸)的大小变化而自动调整。这种自动模式很可能蹦年满足你(或者客户)的要求。 例如左侧的侧边栏,显然太小了。我们可以通过flex-basis来给出其大小:

.leftbar{
    /* ... */
    flex-basis: 500px;
}

.rightbar{
    /* ... */
    flex-basis: 1000px;
}

你会发现尺寸确实发生了变化,当窗口的尺寸超过1500时,两栏的尺寸符合我们的预期。不过如果此时你缩小窗口的尺寸,压缩到1500之内,两个栏的尺寸会同步缩小。

注意一点

flex是一种非常自动化的布局方式。好处是开箱即用,坏处是要精确调整需要非常多的配置。

这里就要理解元素的flex-growflex-shrink的作用。

我们为.rightbar设置flex-grow:

.rightbar{
    /* ... */
    flex-grow: 1;
}

此时你会发现,在窗口尺寸放大时,右侧块也会伴随变大。

.leftbar设置flex-shrink

.leftbar{
    /* ... */
    flex-shrink: 0;
}

你会发现当窗口缩小时,左侧块并不会同步缩小,而是保持原来的尺寸。

块尺寸到底时如何计算的?

这里的计算逻辑略有点绕,但并不难理解。

你可能会注意到对于flex-basis属性,我们设置的时候有px单位,但flex-growflex-shrink里并没有单位——对,这里设置的实际上是比例。

我们可以这样理解:对于flex容器内部的元素,他总是倾向于完全填满整个容器(回忆一下,最初没有配置时,是不是这样的)。

如果容器的尺寸并不等于内部元素的尺寸之和时,内部元素就需要放大或者缩小来保证正好填满整个容器。

那么问题就是,放大或者缩小的空间要如何分配。

举例来说,如果容器内有3个元素,每个元素的flex-basis都是200px,总体要占用600px的空间。

如果容器尺寸是840px,那么有840px-600px=240px的空间需要填充。flex-grow就给出了分配这240px的比例。

假如我们为每个块都设置flex-grow:1,那么显然他就会被平均分配给三个块,每个块增加80px,也就是每个块都变成680px

同理,如果我们为中间的块单独设置flex-grow:2,两边的块维持1,那么两边两个块分60px,中间的块分120px

对于flex-shrink也是类似的,只是分配的变成了每个块要负责缩小多少尺寸。

权威的解释可以查手册:

flex-growopen in new window

flex-shrinkopen in new window

flex-basisopen in new window

最后flex可以对以上三者进行简写,依次按flex-growflex-shrinkflex-basis的顺序排列即可。

例如:flex : 1 0 300px相当于:

    flex-grow : 1;
    flex-shrink : 0;
    flex-basis : 300px;

三条规则。

修饰一下

针对我们上面的要求,我们可以为.leftbar.rightbar加上flex-shrink : 0,并为他们设置不同的flex-basis

.leftbar{
    background-color: aqua;
    flex-basis: 500px;
    flex-shrink: 0;
}

.rightbar{
    background-color: coral;
    flex-basis: 1000px;
    flex-shrink: 0;
}


 
 




 
 

此时缩放页面将不再影响两个块的尺寸了。

决定元素如何(横向)排列的属性是容器的justify-content属性。设置为center则可以将元素横向居中放置。 这个属性要设置在容器、也就是.container上:

.container{
    display: flex;
    justify-content: center;
}


 

就可以实现居中显示了:

居中显示
居中显示

最后我们为两栏设置margin来为两栏创建一个空间来分隔:

.leftbar{
    background-color: aqua;
    flex-basis: 500px;
    flex-shrink: 0;
    margin-right: 20px;
}

.rightbar{
    background-color: coral;
    flex-basis: 1000px;
    flex-shrink: 0;
    margin-left: 20px;
}




 






 

就可以得到我们想要的结果了。

其他资源

其他相关的属性,可以查看上面的手册、教程和视频并自己研究。

再次强调:学习主体是学生,不是老师!

MDN页面上有两个很好的学习资源:

  1. A Complete Guide to Flexboxopen in new window,这是一篇相对比较完善的(英语)教程。
  2. 青蛙游戏open in new window,一个非常有趣的页面游戏,通过flex布局把青蛙放到正确的位置上。妥妥寓教于乐。国内有人做了汉化。

定位模式

绝对定位

你一定经常遇到一些覆盖在页面上怎么都点不掉的图片。或者一些在页面上飘来飘去的内容。这一般是应用了绝对定位

绝对定位的设置

我们页面最下方增加一个div并配置对应的css:

<div class="absolute">
    <h1>
        绝对定位
    </h1>
</div>
.absolute{
    width: 200px;
    background-color: yellow;
    position:absolute;
}



 

这个div会显示在页面的下方,与正常的页面并没有太大区别。

我们还需要为标记设置位置信息,才能放置到我们希望的位置:

.absolute{
    width: 200px;
    background-color: yellow;
    position:absolute;
    left: 100px;
    top:  100px;
}




 
 

绝对定位-在页面上随意放置
绝对定位-在页面上随意放置

你可以尝试调整lefttop对应的值来移动该标记。

z-index属性

你可能会想到,假如有多个绝对定位的块,他们之间可能相互覆盖,那么这种覆盖次序可以被控制吗?

absolute块之间的相互覆盖
absolute块之间的相互覆盖

更进一步,我们知道正常的页面内容会被absolute块覆盖,那么能否反过来呢?

确实是可以的——利用块的z-index属性。所谓z轴,可以视为一个垂直于屏幕向外(朝向观众)的轴,z值越高,就放置于越接近观众的位置。

也就是说,z值高的标记会覆盖z值低的标记。

而正常的页面块处于z=0的平面,也就是说,z值小于0的块会被正常页面遮挡。

试试

自己调整一下块的z-index,看看覆盖效果。

固定定位

绝对定位的位置是相对页面内容的定位。固定定位则是相对窗口的定位,这两者之间相差一个滚动条。

简单说,如果页面滚动,块会同步移动,那么是绝对定位,否则是相对定位。

这种定位通常可以应用于一些希望不管页面如何滚动都会出现的块,例如:返回顶部的按钮,或者页面的目录等等。

试试

把之前的absolute块改成fix的再试试看,注意要充分填充页面内容,保证页面有滚动空间。

你发现了吗

本页面是不是也有类似的块呢?那么他真的是固定定位的吗?

怎么查看?

相对定位

有绝对定位就有相对定位,相对定位可以看作是在本地位置的微调,下面的例子就非常清楚:

相对定位
<div>
    <h1>移动这个<span class="relative"></span></h1>
</div>
.relative{
    color: red;
    position: relative;
    left: -20px;
    top: 20px;
    border: 2px solid black;
}

点击右侧的jsfiddlecodepen按钮可以进入测试页面自行测试。

其他布局方式

在flex布局(从css3才开始支持)之前。人们使用浮动布局来实现分栏,这种布局涉及到相对复杂的页面流的理解,相对难以理解。

你可以查看这里open in new window来获得完整的理解。

不得不承认,有些布局确实flex是无能为力的,例如这个:

浮动布局
浮动布局

但实际上这种需求并不多,并且浮动布局极大地增加了记忆和理解的负担,并需要更多的调试。