前端那么多布局

920 阅读23分钟

review、review 天天 review!

重读MDN查漏补缺~~

另:文中的「TODO」字样,意在提醒我自己这里还有知识点,以后可以用作写博客的主题(或者续写、扩展)

都有哪些布局呢?

正常布局

正常布局流(normal flow)是指在不对页面进行任何布局控制时,浏览器默认的HTML布局方式。

简单说,就是纯html写出来的文档页面,在浏览器中默认显示的布局方式。

那这里有一些方式,可以改变(覆盖)这种默认布局:

  • display属性设置为某些值的时候,比如flexgrid
  • 浮动
  • 定位
  • 表格布局,其实就是 display:table
  • 多列布局

下面先简单描述一下这些东西。

display

在css中实现页面布局的主要方法是设定display属性的值。
Flexbox 是CSS 弹性盒子布局模块(Flexible Box Layout Module)的缩写。它被专门设计出来用于创建横向或是纵向的一维页面布局
Grid布局(Grid Layout)则被设计用于同时在两个维度上把元素按行和列排列整齐

浮动 float

把一个元素“浮动”(float)起来,会改变元素本身在文档流中的行为(俗话说,脱离文档流了),也会改变他后面原本在文档流中的元素的位置(周围的内容会环绕浮动的元素)。

   题外话:浮动一开始设计出来,是为了让文字环绕图片,但现在更多被用来布局。

比如:

<h1>Simple float example</h1>
<div class="box">Float</div>
<p> 
Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
...
</p>

环绕效果: image.png

扩展:传统布局方式

定位

定位和浮动有什么区别呢?

关于是否占位的问题可以看这篇文章css脱离文档流到底是什么意思,脱离文档流就不占据空间了吗?脱离文档流是不是指该元素从dom树中脱离? - 张秋怡的回答 - 知乎
简单说就是,浮动虽然脱离文档流,但是在文档流有占位,其他盒子当作没有这个盒子进行布局排布,但是其他盒子里的文字依然会环绕浮动的元素。
但是定位的话,就是完全脱离了文档流,也没有占位,其他盒子的文字也看不见这个定位了的元素。
定位很少用来大块布局,更多是小范围微调一些特殊项。
默认static就是在文档流;相对定位relative相对元素原本的位置进行微调,比如说为了给一个图标腾个位子;绝对定位absolute相对页面进行定位;固定定位fixed,相对浏览器可视窗口进行定位;粘性定位sticky,静态定位和固定定位的混合,当距离浏览器可视窗口有一定距离的时候,就不动了。

表格布局

就是利用表格的一些特性进行页面的布局(废话文学+1
现在不提倡这么用了。浅浅的了解一下得了。

多列布局

就是把页面划分成一竖列一竖列的。
主要依托于column-count这个属性。

<div class="container">
    <h1>Multi-column layout</h1>
    <p>Paragraph 1.</p>
    <p>Paragraph 2.</p>
</div>

CSS:

.container {
    column-width: 200px;
}

展示:

image.png

正常文档流

正常文档流下面是怎么布局的呢?

默认情况下:
一个块级元素的内容宽度是其父元素的100%,其高度与其内容高度一致。
内联元素的height width与内容一致。

为内联元素设置宽度是无效的。

外边距叠加

一篇很好的文章:深入理解CSS外边距折叠(Margin Collapse)
MDN:外边距重叠

大概总结一下,就是在普通的文档流中,相邻的两个元素,可以是父子、也可以是兄弟,在垂直方向上如果没有border、padding、内容,也没有被BFC、清除浮动来分开一个块级元素的上下边界,那么他们垂直方向上的上下外边距会发生重叠现象,取大的外边距生效。

弹性布局 flex

弹性盒子是一种用于按行或按列布局元素的一维布局方法 。元素可以膨胀以填充额外的空间, 收缩以适应更小的空间。

长久以来,我们更多依靠浮动和定位来解决布局问题,但是这两个很难简单地满足下面这样的场景:

  • 垂直居中
  • 容器里所有子元素能动态分配等量的可用宽度/高度
  • 多列布局中,每一列即使内容d量不同,也能有相同高度

但是弹性布局flex可以简单实现这样的需求。

一个简单的例子:

image.png 仅仅添加一个display:flex就可以得到下面的效果:

image.png

(很神奇,这是因为flex布局的默认值会有这样的效果。

flex模型声明

当外面的元素,表现为 flex框时,里面的元素会按照两个 布局:

image.png

主轴:是沿着flex元素放置的方向延伸的轴,这个轴的开始和结束分别被称为main startmain end
交叉轴:垂直于主轴的轴,开始结束分别为cross startcross end
flex容器:设置了display:flex的元素被称为flex容器
flex项:flex容器里面的子元素们就是一个个, mdn这么说的: 在 flex 容器中表现为柔性的盒子的元素被称之为 flex 项flex item

排个雷:
应该有注意到,上面说主轴交叉轴的时候,其实并没有规定「横着的是主轴竖着的是交叉轴」这样。
实际上呢,主轴总共可以有四个方向,相对应的,交叉轴也有四个方向。

有一个属性是可以控制主轴交叉轴方向的,说这个之前我们也应该知道一件事:

flex的那些属性们,有的是要在flex容器(父元素)上设置的,有些是要在flex项(子元素)上面设置的。
这要分清。

下面我会写一些常用的属性,其实都是说明书类的东西,也没必要讲究太多,但是看得多不比写的多,希望多写写能让我记住一些,更能理解一些。

先说在flex容器上设置的属性

flex-direction

flex-direction就是控制主轴方向的属性。

我们上面说,主轴其实是有四个方向,哪四个方向呢?
从左到右row(默认)、从右到左row-reverse、上到下column、下到上column

image.png

为了方便,我们下面默认主轴方向是左到右,即:「一行」。

flex-wrap

当flex项过多,就会有溢出问题,就像这样:

image.png

这个时候设置flex-wrap:wrap属性可以让flex容器里的flex项实现换行

image.png 看起来是换行了,但好像哪里不对,我们要的是每一行有多列的效果,但是现在似乎…单一个flex项就占据了一整行,这是因为对里面的flex项没有进行宽度的限制,导致他默认占据父元素的100%。
那这怎么办呢?
要为 flex项搭配宽度:

image.png

flex-wrap 有三个属性值:
可以是no-wrap默认不换行;
也可以是wrap,一行不够,下面一行开头接着走;
还有一个是wrap-reverse,一行不够,上面一行接着走

像这样 image.png

flex-flow

他是上面两个属性flex-directionflex-wrap两个属性合并的属性,也可以说是缩写。

flex-direction: row;
flex-wrap: wrap;

可以替换为:

flex-flow: row wrap;

我粗俗的直译一下,即 :flex流动的方向

什么意思呢?流动有两个意思,一个是在平原怎么流动什么方向(direction),一个是该拐弯了望那儿拐(wrap)
(毕竟河流不能一直顺溜直溜的流下去吧。

justify-content

水平对齐方式。

总共有五种:
默认flex-start(默认值):左对齐
flex-end:右对齐
center:居中
space-between:项目两端对齐,中间每个项目的间距相等
space-around:每个项目两端都有一定的间距,所以项目间的间隔比项目和边框之间的间隔大一倍。

下面是从阮一峰老师网站拿的图:

image.png

align-items

交叉轴对齐方式,也有五种:
stretch:默认值,默认占满整个容器的高度
flex-start:和交叉轴的起点对齐
flex-end:和交叉轴的终点对齐
center:项目的中点和交叉轴的中点对齐
baseline:项目里,第一行文字的基线相互对齐。

下面依旧是从阮一峰老师网站拿的图:

image.png

align-content

他有6个值:
stretch:默认占满整个交叉轴
flex-start:和交叉轴起点对齐
flex-end:和交叉轴终点对齐
enter:和交叉轴中点对齐
space-between:两端主轴和交叉轴起点终点对齐,中间主轴和主轴的距离相等
space-around:每个轴线两端都有一定间隔,所以主轴和交叉轴起点终点距离是两个主轴距离的一半

依旧从阮一峰老师网站拿的图:

image.png

其实乍看之下,这个align-content的属性值和上面的align-items是很像的,
align-content是多行(多个主轴)内容中,每一行(每一个主轴)在交叉轴如何排布。如果只有一个主轴,那么这个属性不起作用。
align-items是一行(一个主轴)内容在交叉轴如何排布。

浅浅的举个例子: image.png

再说设置在flex项上的属性

order

我们会想,能不能不改变dom树里flex项的顺序,而改变显示出来的顺序。
比如说好几个项这样排列 【1,2,3】,但现在我希望不改变dom树里的排列,让他显示为 【3,1,2】
这时候order属性就派上用场了。

他的值是纯数字,默认都是0,值越小,排列越靠前,也可以是负值。

图源不必多说,如下: image.png

flex-grow

grow,生长,就是我想让这个flex项变多大就变多大,默认是0

他的值是数字,比如说,别的都是1,就一个是「2」,那它的大小肯定是别的 2 倍。

image.png

flex-shrink

有放大就有缩小,如果所有的项都是默认值flex-shrink:1的时候,一旦缩小就会全部一起缩小。
一旦,其中一个设置成了flex-shrink:0,其他都是1,那么空间缩小的时候,前者不缩小。

他的值也可以是小数,但我不知道除了1和0之外其他数字的效果。

image.png

flex-basis

这个属性定义了flex项在主轴方向的初始width(MDN说的是大小,我认为跟width没什么差)。默认是auto,即项目本来的大小。

Note:  当一个元素同时被设置了除值为 auto 外de  flex-basis  和 width 

(或者在 flex-direction: column 情况下设置了height) ,

flex-basis 具有更高的优先级.

除了<width>值,也可以设置content值,即根据内容自动定义大小,但是有一些浏览器不支持这个属性。(这里不多说,但这个属性也挺有意思,可以看看TODO

image.png

image.png

flex & flex:1

(唱)终于等到你~~还好我没放弃~~~

相信很多人都应该见过flex:1。他到底是什么东西呢?不急,慢慢看~

首先说flex这个属性,他是flex-growflex-shrinkflex-basis这三个属性的简写,默认值是0 1 auto,意思(应该)是默认不放大倍数、缩小也等比例缩小、初始宽度根据项目本身大小决定。TODO

该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。

flex:1 === flex:1 1 0

flex-grow1
flex-shrink1
flex-basis0%

align-self属性

它允许单个项目拥有与其他项目不一样的对齐方式,

默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch

image.png

flex嵌套

要分辨清楚也不难,这个元素是父元素的时候就按照父元素写,如果同时他也是其他元素的子元素,就按照子元素来写。很容易。

网格布局 grid

Flexbox用于设计横向或纵向的布局,而Grid布局则被设计用于同时在两个维度上把元素按行和列排列整齐。

相比于flex,grid自然是更为复杂,当然,我现在还不准备学呢,所以先放放TODO

这里是MDN对网格布局的介绍: 网格
Grid布局

浮动

浮动,一开始是准备让图片浮动,然后文字自动环绕图片,是想实现这样的效果的,但是后来发现「哦豁,不止图片可以浮动哦」,任何东西都可以浮动,所以浮动的使用范围变大了。

使用范围变大之后,浮动被用来实现页面布局,它可以让在源码中本来应该是纵向排列的信息变得可以横向排列。

现在出现了比浮动更好的布局技术,所以浮动也被认为是「传统的布局方法」。

在这一章中,我们仅就浮动这一命令本身的性能展开讲解。

关于浮动产生的文字环绕的两个例子

先看一个简单的例子,了解一下浮动之后,文字环绕是什么样的效果:

<h1>Simple float example</h1>
<img src="..." alt="...">
<p> 
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
    ...
</p>
img {
  float: left;
  margin-right: 30px;
}

文字环绕效果: image.png

MDN中 : 再看我们的首字下沉例子这个例子,首字母没有浮动的时候是下图图一,浮动的时候是下面第二张图(第二张图蓝色圆圈处可以看到打开了浮动

image.png image.png

多列浮动布局

浮动可以制作出来多列布局,虽然可能会产生一些奇奇怪怪的副作用必须要处理。

两列布局

简单说就是两个元素,一个float:left、一个float:right,同时要记得设置两个的宽度width,否则默认宽度100%,这两个元素每个都默认占满一整行

看下面的例子,两个div,一个left一个right,分别设置宽度和浮动(很简单一东西,抄过来个demo纯属为了丰富一下文章诶嘿嘿:)

image.png 设置了宽度之后,才会有下面这样的效果(两个都要设置,否则一个是设置的宽度,另一个就是100%,还是会分成两行 image.png

    固定宽度布局(fixed-width layout):将宽度设置为一个固定值
    设置成百分比就是 流式布局(liquid layout) 

三列布局

有了两列布局之后,搞一搞三列布局就很容易了,如果按照div1 div2 div3的结构写代码,同时想要展示出来的效果也是div1 div2 div3时,我们可以让前两个div左浮,第三个div右浮

另外还有一说,如果想要按照div1 div3 div2的结构写代码,想要展示出来的效果和上面一样是div1 div2 div3时,我们就要让 第一个div左浮,后面两个div右浮

清除浮动

元素浮动之后,就会文字环绕,这样就会引起一个问题,如下图:

image.png 所以我们要使用clear属性清除浮动

清除浮动之后

image.png

浮动所产生的问题

除了上面说的要清除浮动,还有其他的一些问题,比如说:

整个宽度可能难以计算

举个例子,我们给上面那个清除浮动的页面加上下面这三行代码,就会发现页面的样式变得...一言难尽,布局被损坏。

您将看到您的布局已损坏 —— 由于内边距和边界引入的额外宽度,一行容纳不下三列了,因此第三列下降到另外两列之下。 image.png

我们可以通过box-sizing: border-box;盒模型解决。

* {
  box-sizing: border-box;
}

加上这段代码之后,第三个div被压在下面的问题倒是解决了,但是新的问题出现了,看下图绿色框里:footer和最后一个div边框紧挨着

image.png

这看着不是很好看,该怎么解决呢?添加margin-top吗?我们试试看

image.png

我们看上图,似乎外边距并没有起作用。 这是为什么呢?

浮动元素不会撑起父元素的高度
举个例子,就像下图一样:父元素body是没有高度的 image.png 但是换个思路,他们占有空间,不然<footer>也不会被顶到下面了对吧,这么看来这些浮动的子元素就好像那个一支红杏出墙来:)

这个时候,我们给 非浮动元素<footer>添加外边距是没办法(在浮动元素和非浮动元素之间)创建间距的。这也是我们现在要解决的问题。

  • 还有一些关于浮动的奇怪的事情——Chris Coyier优秀的关于Floats文章概述了其他一些以及修复这些。 TODO(有时间可以翻译看看,文章也不算长。

那现在要怎么解决「浮动元素和非浮动元素之间无法通过margin创建边距」这个问题呢?
既然浮动和非浮动无法创建边距,那我两个非浮动之间可以创建边距吧?

具体怎么操作呢?
<footer>和那三个<div>之间加入一个非浮动 空元素

<div class="clearfix"></div> 

其中:类clearFix 是用于清除浮动的,此时<footer>里面用去清除浮动的代码就可以删掉了。

这个时候再用margin就可以生效了。

image.png

浮动项目的背景高度

列的高度是不固定的,就像之前的例子一样。
如果我们设定列的高度为一个固定值,那么!!!难免会有溢出的问题。就像下图一样: image.png 至于怎么处理,仁者见仁智者见智,但是最终结论仍然是flex yyds哈哈哈哈

使用一种叫做伪列(faux columns)的技术——这包括将背景(和边界)从实际的列中提取出来,并在列的父元素上画一个伪造的背景,看起来像列的背景一样。不幸的是,这将无法处理列边界。 详见对于伪列伪列流体布局的教程。TODO

总的来说,清除浮动会让情况变得复杂,我们希望只通过css就能完成布局,如果加上html才能,难免开历史的倒车(那样结构和样式就会耦合严重,我们希望各干各的各司其职

定位

好吧,说实话,文章已经很长了,再考虑到上面 flex和浮动都是我虽然会但不是完全会的内容(简而言之就是对这个技术点掌握不精,所以写的长了点,定位和后面一些内容就简单说吧,简单过过。(如果写的多了那一定是这个知识有复习的必要

简单说,定位就是依靠position这个属性来的,它有几个属性就有几种定位的方式

static默认值,静态定位,意思是正常在文档流中的位置。
relative相对定位,相对它本来的位置发生偏移,它原本的位置在它定位之后不会被后面的元素占据。
absolute绝对定位,相对最近一个position不是static的祖先元素进行定位,如果每一个祖先元素都是satic,那它就相对根元素定位。它定位之后,后面的元素也会顶上来,把它原本的位置占据了。
fixed固定定位,相对于浏览器视口进行定位。
sticky粘性定位,静态定位和固定定位的结合。当元素没有到预设位置的时候,就按静态定位走,到达预设位置之后,就不动了。

此外,可以通过z-index设置元素的层叠。

多列布局

本质上是对属性的一些应用(当然哪些布局不是呢我这话说的。
不过多列布局这几个属性简单点就是

父元素:
column-count: 3; // 将内容拆分成三列
column-width: 200px; // 控制每一列的宽度(不能单独指定每一列多宽
column-gap: 20px; // 改变列与列之间的间隙
column-rule: 4px dotted rgb(79, 185, 227); // 类似border属性,列与列之间的分割线

子元素
break-inside: avoid; // 确保列的内容不会折断
page-break-inside: avoid; // 上面那个属性的旧版本

响应式布局

响应式布局因媒体查询而火起来,下面就是一个典型的媒介查询的案例:
屏幕宽度至少800px的时候,就会触发下面的样式(就是屏幕宽度要大于800px

@media screen and (min-width: 800px) {
  .container {
    margin: 10px;
  }
} 

其他关于响应式布局,也就是使用一些技巧,比如vw、vh、rem、em等等,或者其他的布局方式,比如网格布局,比如max-min-这些属性。

没什么可说的。

看到一个这个很有意思: 视口元标签

在文档的<head>看到下面的<meta>标签。

<meta name="viewport" content="width=device-width,initial-scale=1">

这个元标签是告诉浏览器,页面的宽度应该等于设备的宽度,之所以要这样是怕浏览器用电脑桌面上很大的一个宽度当作设备宽度,并依次渲染页面,这样显示在手机浏览器上的就是缩小版的桌面网页了,体验不好。

如果有了这个元标签,浏览器将按照手机视口的宽度渲染页面,这时候那些媒体查询也能起作用,能更好的适配手机端。

点此查看更多<meta name="viewport"> 的 content 属性的值

媒体查询入门指南

OK,本来觉得这篇文章够长了,所以想再开一个,但是另开一个总会「明天吧、明天吧...」这个样子,干脆一鼓作气学完好了。

首先看一个最简单的媒体查询应该是什么样子的:

@media 媒体类型 and (媒体表达式) {
  一组CSS规则
}

一个媒体类型 media-type,浏览器通过这个匹配和段代码是用在什么类型的媒体上,是屏幕上,还是打印的印刷品上。
关于媒体类型有很多啊,可以点这里查看,但是常用的可能也就所有类型all、打印print、计算机屏幕screen、语音合成器speech(这个语音合成器我也没怎么见过说实话
如果没有指定所用的媒体类型,那么 默认是all

一个媒体表达式 media-feature-rule,告诉浏览器什么情况下,里面的css规则会生效。

一组CSS规则,当规则生效的时候,这组CSS规则就会起作用。

当然,我们也可以只指定媒体类型,就像下面这样,我们只在页面被打印的时候应用下面这段样式:

@media print {
    body {
        font-size: 12pt;
    }
}

媒体表达式

指定媒体类型之后,我们也可以用一条规则去指定媒体特征
比如说我们指定媒体类型是screen,在这个基础上我们指定「当屏幕宽度至少800px的时候,下面这些CSS生效」,这个媒体特征规则就是屏幕宽度800以上。

可以在这里查看更多媒体特性,这里就说几个常用的:

宽和高

为了建立响应式设计,我们使用min-widthmax-widthwidth媒体特征指定屏幕宽度在多少多少之间的时候应用某一套样式。

朝向

orientation可以区分屏幕是横放 landscape mode还是竖放 portrait mode
下面就是当屏幕横放的时候,应用下面的样式

@media (orientation: landscape) {
    body {
        color: red;
    }
}

使用指点设备

什么是指点设备呢?先说答案:就是手机、pad这些,他们没有鼠标,点一下就是点击,没有那种悬浮效果。

像电脑屏幕,就会有悬浮效果。

如果我们知道用户没有hover效果,我们可以设置更大的交互范围,因为手指触屏可能没办法像鼠标一样精准。

hover的意思是:主要输入模式是否允许用户在元素上悬停
它可以检查设备有没有鼠标悬停的效果,
同时,除了 hover ,我们可以使用 pointer 这个媒体特性来做规则限制。
pointer是什么意思呢:主要输入机制是一个指针设备吗?如果是,它的精度如何?

pointer可以取三个值: none:用户根本没有指针使用,可能仅仅是命令行,使用键盘进行操控,或者是语音设备;
fine:就是指针类的东西,比如说我们的鼠标;
coarse:就是手指这样,使用场景是手机、pad等。

媒体查询中的与、或、非

简单事情简单说:
:使用and连接 「媒体类型 + 媒体表达式」所构成的规则们,比如下面:
下面是一个媒体类型 and 媒体表达式 and 媒体表达式样子。
表达的意思是:视口至少为400像素宽,且设备横放

@media screen and (min-width: 400px) and (orientation: landscape) {
    body {
        color: blue;
    }
}

:使用逗号,连接「媒体类型 + 媒体表达式」所构成的规则们,比如下面这样:
是一个媒体类型 and 媒体表达式 , 媒体类型 and 媒体表达式的样子,其实可以看到,与和或是可以一起使用的
下面表达的意思是:视口至少为400像素宽的时候 或者 设备处于横放状态

@media screen and (min-width: 400px), screen and (orientation: landscape) {
    ...
}

:使用not进行连接
他会直接反转整个媒体查询的含义
比如下面这个含义是:只会在朝向为竖着的时候生效
(landscape 意思是 横放)

@media not all and (orientation: landscape) {
    ...
}

断点

媒体查询的断点是什么?应该打在哪里?

所谓断点,就是引用媒体查询的地方,我自己的理解是,min-width : 800px里,这个 800px就是断点。

至于应该打在哪里,应该移动优先,当超过一定范围的时候,按照移动端的样式去做。

写在最后

断断续续写的时间也不短了,后面其实还有关于「传统布局」和「浏览器支持」的一些东西。TODO

大概是介绍了一下古早的一些布局方式是怎么做的,以及说了一下浏览器虽然支持但是并不完全一样的道理。

那么就这样吧~加油加油加油!!

参考

flex部分图源以及内容参考: 阮一峰 # Flex 布局教程:语法篇