worldsite / blog.sc

Blogging soul chat, stay cool. via: https://blog.sc
3 stars 0 forks source link

CSS的布局方式 #33

Open suhao opened 3 years ago

suhao commented 3 years ago

前端开发,使用CSS进行布局是工作的核心。在做ReactNativeDesktop的实践中,逐步学习了CSS的布局方式。本文将汇总相关的基础知识,以供回顾和总结。

一、盒模型布局方式

传统的布局方式是通过盒模型,使用display(文档流布局)+position(定位布局)+float(浮动布局)来控制。

1. 文档流布局

按照文档的顺序一个个显示,块元素独占一行,行内元素共享一行。

2. 浮动布局

使元素脱离文档流,浮动起来。

3. 定位布局

通过position属性来定位。

二、Flexbox布局

通过上述盒模型的三种布局方式有一些缺陷,比如我们不能只使用一个属性来实现垂直居中布局,所以就产生了第四种布局方式:flex 布局。可以简便、完整、响应式地实现各种页面布局,Flex即Flexible Box的缩写,意为弹性布局,为盒模型提供最大的灵活性。任何一个容器都可以指定为flex布局,行内元素也可以通过inline-flex属性值来使用flex布局。inline-flex 和 inline-block 一样,对设置了该属性值的元素的子元素来说是个 display:flex 的容器,对外部元素来说是个 inline 的块。

在 flex 中,最核心的概念就是容器和轴,所有的属性都是围绕容器和轴设置的。其中,容器分为父容器和子容器。轴分为主轴和交叉轴(主轴默认为水平方向,方向向右,交叉轴为主轴顺时针旋转 90°)

Flexbox布局

在使用 flex 的元素中,默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis),主轴开始的位置称为 main start,主轴结束的位置称为 main end。同理,交叉轴开始的位置称为 cross start,交叉轴结束的位置称为 cross end。在使用 flex 的子元素中,占据的主轴空间叫做 main size,占据的交叉轴空间叫做 cross size。

这里需要强调,不能先入为主认为宽度就是 main size,高度就是 cross size,这个还要取决于你主轴的方向,如果你垂直方向是主轴,那么项目的高度就是 main size。

实现 flex 布局需要先指定一个容器,任何一个容器都可以被置顶为 flex 布局,这样容器内部的元素就可以使用 flex 来进行布局。简单说来,如果你使用块元素如 div,你就可以使用 flex,而如果你使用行内元素,你可以使用 inline-flex。

需要注意的是:当时设置 flex 布局之后,子元素的 float、clear、vertical-align 的属性将会失效。

1. Flex容器

设置了 flex 属性的容器可以通过设置其属性值来设置容器的子元素的排列规则:

2. Flex子元素

三、Grid布局

网格布局,实现二维布局方式。可以让我们摆脱现在布局中存在的文档流限制,换句话说,你的结构不需要根据设计稿从上往上布置了。这也意味着您可以自由地更改页面元素位置。这最适合你在不同的断点位置实现你最需要的布局,而不再需要为响应你的设计而担心HTML结构的问题。

和 table 布局不同的是,grid 布局不需要在 HTML 中使用特定的标签布局,所有的布局都是在 CSS 中完成的,你可以随意定义你的 grid 网格。

没有 HTML 结构的网格布局有助于使用流体、调整顺序等技术管理或更改布局。通过结合 CSS 的媒体查询属性,可以控制网格布局容器和他们的子元素,使用页面的布局根据不同的设备和可用空间调整元素的显示风格与定位,而不需要去改变文档结构的本质内容。

1. 网格线:Grid Lines

网格线组成了网格,他是网格的水平和垂直的分界线。一个网格线存在行或列的两侧。我们可以引用它的数目或者定义的网格线名称

GridLines

2. 网格轨道:Grid Track

网格轨道是就是相邻两条网格线之间的空间,就好比表格中行或列。在网格中其分为grid column和grid row。每个网格轨道可以设置一个大小,用来控制宽度或高度。

GridTrack

3. 网格单元格:Grid Cell

网格单元格是指四条网格线之间的空间。所以它是最小的单位,就像表格中的单元格。

GridCell

4. 网格区域:Grid Area

网格区域是由任意四条网格线组成的空间,所以他可能包含一个或多个单元格。相当于表格中的合并单元格之后的区域。

GridArea

5. Grid布局

使用 grid 布局很简单,通过display属性设置属性值为 grid 或 inline-grid 或者是 subgrid(该元素父元素为网格,继承父元素的行和列的大小) 就可以了。

网格容器中的所有子元素就会自动变成网格项目(grid item),然后设置列(grid-template-columns)和 行(grid-template-rows)的大小,设置 grid-template-columns 有多少个参数生成的 grid 列表就有多少个列。

注:当元素设置了网格布局,column、float、clear、vertical-align属性无效。

如果没有设置 grid-template-columns,那么默认只有一列,宽度为父元素的 100%。设置了 grid-template-columns 的话,设置了几个参数,就有几列(不超过 grid item 的个数),然后设置的 grid-template-row 参数就是每一列的高度(超出列数的高度无效)。

例如:grid-template-columns: 1fr 1fr 2fr; 生成3列

css fr 单位是一个自适应单位,fr单位被用于在一系列长度值中分配剩余空间,如果多个已指定了多个部分,则剩下的空间根据各自的数字按比例分配。fr 是基于网格容器可用空间来计算的(flex 也是一样),所以我们可以和其他单位混合使用,如果需要的话。

例如:grid-template-rows: minmax(100px,200px) minmax(50px,200px),将第一行的高度设置为 minmax(100px,200px),第二行的高度设置为minmax(50px,200px)

如果容器总高度设置为 300px,这时每一列的高度要怎么算呢?总高度是小于第一列高度的最大值和第二列高度的最大值之和的,这样就是先用 总高度 300px - 第一列最小高度 100px - 第二列最小高度 50px = 150px。第一列高度:第一列最小高度 100px + 150px/2 = 175px;第二列高度:第一列最小高度 50px + 150px/2 = 125px

6. 通过网格线定位 grid item

我们可以通过表格线行或者列来定位 grid item,grid-row 是 grid-row-start 和 grid-row-end 的简写。grid-column 是 grid-column-start 和 grid-column-end 的简写。如果只提供一个值,指定了 grid-row-start 和 grid-column-start 的值。如果提供两个值,第一个值是 grid-row-start 或者 grid-column-start 的值,第二个值是 grid-row-end 或者 grid-column-end 的值,两者之间必须要用/隔开。这四个值可以用 grid-area 缩写,分别对应 grid-row-start、grid-column-start、grid-row-end、grid-column-end。

7. 合并单元行与合并单元列

和 excel 中的合并单元行/列是相同的(这个需要设置在 grid item 中),grid-column-start、grid-column-end、grid-row-start、grid-row-end,也可以使用 grid-row 和 grid-column 简写的形式,关键词 span 后面紧随数字,表示合并多少个列或行,/ 前面是从第几行/列开始。

8. 自定义网格线名称

在 grid 中,是可以自定义网格线的名称的,然后使用定义好的网格线来进行布局,[col1-start] 网格线名称一定要使用 [] 括住

9. 通过网格区域命名和定位网格项目

什么是网格区域? 网格区域(grid-area)是一个逻辑空间,主要用来放置一个或多个网格单元格(Grid Cell)。他是由四条网格线(Grid line),网格区域每边一条,四边相交组织的网格轨道(Grid Track)。简单点理解,网格区域是有四条网格线交织组成的网格空间,这个空间中可能是一个网格单元格,也可能是多个网格单元格。

定义网格区域 在CSS Grid Layout中定义网格区域有两种方式,一种是通过网格线来定义,另一种是通过grid-template-areas来定义。接下来看看两种定义网格区域的方法在具体使用过程中有何不同。

四、常用的CSS布局

1. 水平垂直居中

垂直居中真的是已经被讲烂了,这个方式是有很多的,但就是看的太多会导致最后一个都没有记住,所以最好是每一种情况只记住一个最佳实践。

对于居中,不需要背什么“x 种方式实现 xx”这样的例子,我们只需要了解其原理即可写出符合要求的 css。

水平、垂直居中,比较喜欢用绝对定位的方法实现,其次就是使用 table 布局,因为自带垂直居中。如果是单行的行内元素使用 line-height 等于 height,对于多行元素的垂直居中,大部分都是使用 table 元素(求推荐更好的布局),当然还有 flex 和 grid 布局

一般水平居中还是比较容易的,一般都是先看子元素是固定宽度还是宽度未知;如果是固定宽度,这种方式是绝对定位居中,除了使用 margin,我们还可以使用 transform;宽度未知,将子元素设置为行内元素,然后父元素设置 text-align: center。

多个块状元素,上面的方式即使子元素不止一个也想实现水平居中也是有效的,(宽度固定不固定都可,不固定的话就不需要设置宽度,会被自动撑开,但是要考虑到撑爆的情况)。当然也可以使用我们刚刚介绍的 flex,我们只需要让子元素在主轴上的对齐方式设置为居中就可以。

单行行内元素,只需要将子元素的行高等于高度就可以了。多行的行内元素,因为给每一个子元素都设置了 line-height,但是试了很多方法,要不是没有效果,要不然就是又局限性,提到最多的是使用 table-cell 的方式(但是貌似这个方法也有一点弊端,那就是其子元素的表现形式和行内元素类似,子元素不能独占一行);还有一个方法是设置一个空的行内元素,使其 height:100%,display:inline-block,vertical-align: middle; 并且 font-size:0。但是这样方式的原理我还不是很清楚,只是知道要设置一个空元素,高度和父元素相等,并且设置垂直居中的属性。但是,这只是用与所有的行内元素的宽度和不超过父元素的宽度的情况。另一个一劳永逸的方法就是 flex。

经常有看到设计稿是图片和文字垂直居中的,那么怎么才能让图片和文字垂直居中呢?只需要给图片一个 vertical-align: middle; 属性就可以。

我们要实现水平或者垂直居中,应该从两方面下手:元素自带居中的效果或者强制让其显示在中间。

所以我们先考虑,哪些元素有自带的居中效果,最先想到的应该就是 text-align:center 了,但是这个只对行内元素有效,所以我们要使用 text-align:center 就必须将子元素设置为 display: inline; 或者 display: inline-block;;

接下来我们可能会想既然有 text-align 那么会不会对应也有自带垂直居中的呢,答案是有的 vertical-align:,我一直不是很喜欢使用这个属性,因为十次用,9.9 次都没有垂直居中,一度让我怀疑人生。现在貌似也搞得不是很清楚,看了 张鑫旭的文章 居然看得也不是很懂,笑哭。目前就在 table 中设置有效,因为 table 元素 的特性,打娘胎里面带的就是好用。还有一种可以有效的方式是前面提到的空元素的方式,不过感觉多设置一个元素还不如使用 table。

还有一只设置垂直居中的是将行内元素的 line-height 和 height 设置为相同(只适用于单行行内元素)

固定宽度或者固定高度的情况个人认为设置水平垂直居最简单,可以直接使用绝对定位。使用绝对定位就是子元素相对于父元素的位置,所以将父元素设置 position:reletive 对应的子元素要设置 position:absolute,然后使用 top:50%;left:50%,将子元素的左上角和父元素的中点对齐,之后再设置偏移 margin-top: 1/2 子元素高度;margin-left: 1/2 子元素宽度;。这种方式也很好理解。

上面的绝对定位方法只要将 margin 改为 transform 就可以实现宽度和高度未知的居中(兼容性啊兄弟们!(ಥ_ಥ))transformX:50%;transformY:50%;

2. 圣杯布局

其实我还真是第一次听说圣杯布局这种称呼,看了下这个名字的由来,貌似和布局并没有什么关系,圣杯布局倒是挺常见的三栏式布局。两边顶宽,中间自适应的三栏布局。

这个布局方式的关键是怎么样才能使得在伸缩浏览器窗口的时候让中间的子元素宽度改变。可以适应浏览器的宽度变化使用百分比设置宽度再合适不过,所以我们要将中间子元素的宽度设置为 100%,左边和右边的子元素设置为固定的宽度。

这里我们要注意的是,中间栏要在放在文档流前面以优先渲染。

将其三个元素的宽度和高度设置好,然后都设置为 float:left。

我们可以看出,现在三个子元素是在一排显示的,因为我们给中间的子元素设置的宽度是 100%,并且中间的子元素在文档流的最前面,最先被渲染。 那么我们要使得三个元素在同一排显示。接下来我们要将 .left 和 .right 向上提。实际上我们是使用 margin-left 为 负值来实现的,我们将 .left 的 margin-left 设置为 -100%(负的中间子元素的宽度),这样,左边的元素就会被“提升”到上一层。 然后就是右边子元素了,只需要设置 margin-left 设置为负的自身的宽度。

现在中间的子元素被遮挡了,只要使得中间的子元素显示的宽度刚好为左边元素和右边元素显示中间的宽度就可以。同时我们还必须保证是使用的半分比的布局方式。

这样的话有一种方式可以即使中间的宽度减少,又可以使中间的宽度仍然使用 100%,那就是设置父元素的 padding 值,将父元素的 padding-left 设置为左边子元素的宽度,将父元素的 padding-right 设置为右边子元素的宽度。

中间的子元素确实是在中间了,那么我们只需要设置相对位置,将左边的子元素和右边的子元素向两边移动就好。

3. 双飞翼布局

双飞翼布局是为了解决圣杯布局的弊端提出的,如果你跟我一起将上面的圣杯布局的代码敲了一遍,你就会发现一个问题,当你将浏览器宽度缩短到一定程度的时候,会使得中间子元素的宽度比左右子元素宽度小的时候,这时候布局就会出现问题。所以首先,这提示了我们在使用圣杯布局的时候一定要设置整个容器的最小宽度。

圣杯布局和双飞翼布局解决问题的方案在前一半是相同的,也就是三栏全部float浮动,但左右两栏加上负margin让其跟中间栏div并排,以形成三栏布局。

不同在于解决”中间栏div内容不被遮挡“问题的思路不一样:圣杯布局,为了中间div内容不被遮挡,将中间div设置了左右padding-left和padding-right后,将左右两个div用相对布局position: relative并分别配合right和left属性,以便左右两栏div移动后不遮挡中间div。

双飞翼布局,为了中间div内容不被遮挡,直接在中间div内部创建子div用于放置内容,在该子div里用margin-left和margin-right为左右两栏div留出位置。

所以只是一个小小的改动,在我们将中间元素宽度调到比两边元素小的时候,也是可以正常显示,但是如果总宽度小于左边元素或者右边元素的时候,还是会有问题。

参考文档