如何实现水平垂直居中可以说是CSS面试题中的经典面试题,在多年前这个面试题给很多同学都带来了困惑,但Flexbxo布局模块和CSS Grid布局模块的到来,可以说实现水平垂直居中已是非常的容易。
在Flexbox布局模块中,不管是单行还是多行,要让它们在容器中水平垂直居中都是件易事,而且方法也有多种。最常见的是在Flex容器上设置对齐方式,在Flex项目上设置 margin:auto。
你可能已经知道在Flex容器上设置 justify-content、align-items 的值为 center 时,可以让元素在Flex容器中达到水平垂直居中的效果。来看一个示例:
<!-- HTML --><div class="flex__container"> <div class="flex__item"></div></div>/* CSS */.flex__container { display: flex; justify-content: center; align-items: center;}
这种方式特别适应于让Icon图标在容器中水平垂直居中,不同的是在Icon图标容器上显示设置display: inline-flex。比如下面这个示例:
<!-- HTML --><div class="flex__container"> <svg> </svg></div>/* CSS */.flex__container { display: inline-flex; align-items: center; justify-content: center;}
在这种模式之下,如果要让多个元素实现水平垂直居中的效果,那还需要加上 flex-direction: column,比如:
<!-- HTML --><div class="flex__container"> <div class="avatar">:)</div> <div class="media__heading"></div> <div class="media__content"></div> <div class="action"></div></div>/* CSS */.flex__container { display: flex; flex-direction: column; justify-content: center; align-items: center;}
在Flexbox布局中,还可以像下面这样让Flex项目在Flex容器中达到水平垂直居中的效果:
<!-- HTML --><div class="flex__container"> <div class="flex__item"></div></div>/* CSS */.flex__container { display: flex; // 或inline-flex justify-content: center;}.flex__item { align-self: center;}
.flex__container { display: flex; // 或inline-flex justify-content: center;}.flex__container > * { align-self: center;}
除此之外,还可以使用 place-content: center 让Flex项目实现水平垂直居中:
.flex__container { display: flex; place-content: center;}.flex__item { align-self: center;}
.flex__container { display: flex; place-content: center; place-items: center;}
这两种方式同样适用于Flex容器中有多个Flex项目的情景:
.flex__container { display: flex; flex-direction: column; place-content: center;}.flex__container > * { align-self: center;}// 或.flex__container { display: flex; flex-direction: column; place-content: center; place-items: center;}
.flex__container { place-content: center; place-items: center;}
.flex__container { align-content: center; justify-content: center; align-items: center; justify-items: center;}
.flex__container { display: flex; align-items: center; justify-content: center;}// 多行.flex__container { display: flex; flex-direction: column; align-items: center; justify-content: center;}
如果在Flex容器中只有一个Flex项目,还可以显式在Flex项目中显式设置 margin 的值为auto,这样也可以让Flex项目在Flex容器中水平垂直居中。例如:
.flex__container { display: flex; // 或 inline-flex}.flex__item { margin: auto;}
整个过程,你可以通过下面这个示例来体验。尝试着选中不同方向的 margin 值:
CSS Grid布局可以说是现代Web布局中的银弹。它也是到目前为止布局系统中唯一一个二维布局系统。
在CSS Grid布局中,只需要仅仅的几行代码也可以快速的帮助我们实现水平垂直居中的效果。比如下面这个示例:
<!-- HTML --><div class="grid__container"> <div class="grid__item"></div></div>/* CSS */.grid { display: grid; // 或 inline-grid place-items: center}
在CSS Grid布局模块中,只要显式设置了 display: grid(或 inline-grid)就会创建Grid容器和Grid项目,也会自动生成网格线,即行和列(默认为一行一列)。
在没有显式地在Grid容器上设置 grid-template-columns 和 grid-template-rows,浏览器会将Grid容器默认设置为Grid内容大小:
这种方法也适用于CSS Grid容器中有多个子元素(Grid项目),比如:
<!-- HTML --><div class="grid__container"> <div class="avatar">:)</div> <div class="media__heading"></div> <div class="media__content"></div> <div class="action"></div></div>
而且 palce-items 适用于每个单元格。这意味着它将居中单元格的内容。比如下面这个示例:
<!-- HTML --><div class="grid__container"> <div class="grid__item"> <h3>Special title treatment</h3> <p>With supporting text below as a natural lead-in to additional content.</p> <div class="action">Go somewhere</div> </div></div>/* CSS */.grid__container { display: grid; place-items: center; grid-template-columns: repeat(2, 1fr); gap: 2vh;}.grid__item { display: grid; place-items: center;}
等高布局也是Web中非常常见的一种布局方式,而且实现等高布局的方案也有很多种。这里我们主要来看Flexbox布局模块和Grid布局模块给我们带来了什么样的变化。
在Flexbox和Grid布局模块中,让我们实现等高布局已经是非常的简单了,比如:
<!-- Flexbox --><flex__container> <flex__item></flex__item> <flex__item></flex__item> <flex__item></flex__item></flex__container>/* CSS */.flex__container { display: flex; // 或 inline-flex}
简单地说,在容器上显式设置了 display 的值为 flex 或 inline-flex,该容器的所有子元素的高度都相等,因为容器的 align-items 的默认值为 stretch。
<!-- HTML --><grid__container> <grid__item></grid__item> <grid__item></grid__item> <grid__item></grid__item></grid__container>/* CSS */.grid__container { display: grid; grid-template-columns: 20vw 1fr 20vw; /* 根据需求调整值*/}
如果需求有所调整,比如在Flex项目 或 Grid项目的子元素高度和容器高度相同。
<!-- HTML --><flex__container> <flex__item> <content></content> </flex__item></flex__container>/* CSS */.flex__container { display: flex;}.content { height: 100%}// 或.grid__container { display: grid; grid-auto-flow: column;}.content { height: 100%;}
Sticky Footer实现方案和等高、垂直居中一样,同样有很多种方案可以实现。
<!-- HTML --><header></header><main></main><footer></footer>
body { display: flex; flex-direction: column;}footer { margin-top: auto;}
可以尝试着在 main 区域右下角向下拖动,改变主内容区域的高度,你会发现“当内容不足一屏时,<footer> 会在页面的最底部,当内容超出一屏时,<footer> 会自动往后延后”。
在Flexbox布局中,还可以在 <main> 区域上设置下面的样式,达到相等的效果:
body { display: flex; flex-direction: column;}main { flex: 1 0 auto;}
<main> 中的 flex: 1 0 auto 相当于是:
main { flex-grow: 1; /*容器有剩余空间时,main区域会扩展*/ flex-shrink: 0; /*容器有不足空间时,main区域不会收缩*/ flex-basis: auto; /*main区域高度的基准值为main内容自动高度*/}
如果你想省事的话,可以在 main 上显式设置 flex-grow:1,因为 flex-shrink 和 flex-basis 的默认值为 1 和 auto。
在CSS Grid布局中我们可以借助 1fr 让 <main> 区域根据Grid容器剩余空间来做计算。
.grid__container { display: grid; grid-template-rows: auto 1fr auto;}
在Web布局中,很多时候会对列做均分布局,最为常见的就是在移动端的底部Bar,比如下图这样的一个效果:
在Flexbox和Grid还没出现之前,如果希望真正的做到均分效果,可以用 100%(或 100vw)除以具体的列数。比如:
<!-- HTML --><container> <column></column> <column></column> <column></column></container>/* CCSS */.container { inline-size: 50vw; min-inline-size: 320px; display: flex-row;}.column { float: left; width: calc(100% / 3);}
在Flexbox和Grid布局中,实现上面的效果会变得更容易地多。先来看Flexbox中的布局:
<!-- HTML --><flex__container> <flex__item></flex__item> <flex__item></flex__item> <flex__item></flex__item></flex__container>/* CSS */.flex__container { inline-size: 50vw; display: flex;}.flex__item { flex: 1;}
在Flexbox布局模块中,当flex取的值是一个单值(无单位的数),比如示例中的 flex:1,它会当作显式的设置了 flex-grow: 1。浏览器计算出来的 flex:
<!-- HTML --><grid__container> <grid__item></grid__item> <grid__item></grid__item> <grid__item></grid__item></grid__container>/* CSS */.grid__container { display: grid; grid-template-columns: repeat(3, 1fr); /*这里的3表示具体的列数*/}
修复这种现象最简单的方式是在Flex容器或Grid容器显式设置一个 min-width(或 min-inline-size):
.flex__container { min-inline-size: 300px;}
不过话又说回来,比如我们的Flex项目(或Grid项目)是一个卡片,每张卡片宽度是相等之外,更希望容器没有足够空间时,Flex项目(或Grid项目)会自动断行排列。
.flex__container { display: flex; flex-wrap: wrap;}.flex__item { flex: 0 1 calc((100vw - 18vh) / 4); /* calc(100vw -18vh) / 4 是flex-basis的基准值 */}
你可以尝试着调整浏览器的视窗宽度,当浏览器的视窗越来越小时,Flex容器宽度也就会越来越小,当Flex容器小到没有足够的空间容纳四个Flex项目(就此例而言),那么Flex项目就会断行排列:
.flex__item { flex: 0 0 400px;}
这个时候,当Flex容器没有足够空间时,Flex项目会按 flex-basis: 400px 计算其宽度,Flex容器没有足够空间时,Flex就会断行:
.flex__item { flex: 1 0 400px;}
当Flex容器没有足够空间排列Flex项目时,Flex项目会按 flex-basis: 400px 计算其宽度,Flex会断行,并且同一行出现剩余空间时,Flex项目会扩展,占满整个Flex容器:
在Grid中实现类似的效果要更复杂一点。可以使用 repeat() 函数,1fr 以及 auto-fit 等特性:
.grid__container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2vh;}
如果你对这方面知识感兴趣的话,还可以移步阅读《Container Query Solutions with CSS Grid and Flexbox》一文。
其实在Grid中与 auto-fit 对比的值还有一个叫 auto-fill。但两者的差异是非常地大,用下图来描述 auto-fit 和 auto-fill 的差异:
另外这种方式也是到目前为止一种不需要借助CSS媒体查询就可以实现响应式布局效果。
圣杯布局(Holy Grail Layout))是Web中典型的布局模式。看上去像下图这样:
对于圣杯布局而言,HTML结构是有一定的要求,那就是内容为先:
<!-- HTML --><header></header><main> <article></article> <!-- 主内容 --> <nav></nav> <aside></aside></main><footer></footer>
在这里主要还是和大家一起探讨,如何使用Flexbox和Grid布局模块来实现圣杯布局。先来看Flexbox实现方案:
body { width: 100vw; display: flex; flex-direction: column;}main { flex: 1; min-height: 0; display: flex; align-items: stretch; width: 100%;}footer { margin-top: auto;}nav { width: 220px; order: -1;}article { flex: 1;}aside { width: 220px;}
nav { order: 0;}aside { order: -1;}
在上例的基础上,借助CSS媒体对象的特性,可以很容易实现响应式的圣杯布局效果:
@media screen and (max-width: 800px) { main { flex-direction: column; } nav, aside { width: 100%; }}
在Grid布局模块中,实现圣杯布局要比Flexbox布局模块中更容易,而且更灵活。在CSS Grid布局模块中,HTML结构可以更简洁:
<!-- HTML --><body> <header></header> <main></main> <nav></nav> <aside></aside> <footer></footer></body>
在CSS方面有很多种方案可以实现圣杯布局效果。我们先来看第一种:
body { display: grid; grid-template: auto 1fr auto / 220px 1fr 220px;}header { grid-column: 1 / 4;}main { grid-column: 2 / 3; grid-row: 2 / 3;}nav { grid-column: 1 / 2; grid-row: 2 / 3;}aside { grid-column: 3 / 4; grid-row: 2 / 3;}footer { grid-column: 1 / 4;}
和Flexbox布局类似,在媒体查询中可以改变每个网格区域的位置:
@media screen and (max-width: 800px) { body { grid-template-rows: auto; grid-template-columns: auto; } header, main, nav, aside, footer { grid-column: 1 / 2; min-height: auto; } main { grid-row: 3 / 4; margin: 0; } nav { grid-row: 2 / 3; } aside { grid-row: 4 / 5; } footer { grid-row: 5 / 6; }}
body { display: grid; grid-template-areas: "header header header" "nav main aside" "footer footer footer";}header { grid-area: header;}main { grid-area: main;}nav { grid-area: nav;}aside { grid-area: aside;}footer { grid-area: footer;}@media screen and (max-width: 800px) { body { grid-template-areas: "header" "nav" "main" "aside" "footer"; }}
如果我们希望 <main> 的区域变得更大,那么可以在 grid-template-areas 上做个调整:
body { display: grid; grid-template-areas: "header header header header header" "nav main main main aside" "footer footer footer footer footer";}
虽然在效果有所调整了,但还是均分状态。更好的解决方案是,将 grid-template-areas 和 grid-template 结合起来使用:
body { display: grid; grid-template-areas: "header header header" "nav main aside" "footer footer footer"; grid-template-columns: 220px 1fr 220px; grid-template-rows: auto 1fr auto;}header { grid-area: header;}main { grid-area: main;}nav { grid-area: nav;}aside { grid-area: aside;}footer { grid-area: footer;}@media screen and (max-width: 800px) { body { grid-template-areas: "header" "nav" "main" "aside" "footer"; grid-template-columns: 1fr; grid-template-rows: auto auto 1fr auto auto; } main { margin-left: 0; margin-right: 0; }}
12列网格布局在设计系统和CSS Framework中经常使用,比如业内经典的Bootstrap就采用了12列网格布局系统:
在社区中也有很多在线工具,帮助我们快速构建12列网格系统,比如 Free CSS Grid Tools & Resources For Developers 一文中罗列的工具。
不过这里主要是想和大家一起看看在Flexbox和Grid布局模块中是如何实现12列的网格布局系统。
先来看Flexbox布局模块。12列网格布局的HTMl结构一般类似于下面这样:
<!-- HTML --><flex__grid> <flex__row> <flex__item col4></flex__item col4> <flex__item col4></flex__item col4> <flex__item col4></flex__item col4> </flex__row></flex__grid>
注意,12列网格中,一般同一行的列数值和刚好等于12。比如上面的HTML结构,行中有三列,每列的宽度刚好四个网格宽度加两个列间距。并且在计算的时候有一套成熟的计算公式:
这些的差异对于计算公式和样式代码的设计都略有差异。我们用其中一个为例:
:root { --gutter: 10px; --columns: 12; --span: 1;}.flex__container { display: flex; flex-direction: column; padding-left: var(--gutter); padding-right: var(--gutter);}.flex__row { display: flex; margin-left: calc(var(--gutter) * -1); margin-right: calc(var(--gutter) * -1);}.flex__row + .flex__row { margin-top: 2vh;}.flex__item { flex: 1 1 calc((100% / var(--columns) - var(--gutter)) * var(--span)); margin: 0 var(--gutter);}.flex__item1 { --span: 1;}.flex__item2 { --span: 2;}.flex__item3 { --span: 3;}.flex__item4 { --span: 4;}.flex__item5 { --span: 5;}.flex__item6 { --span: 6;}.flex__item7 { --span: 7;}.flex__item8 { --span: 8;}.flex__item9 { --span: 9;}.flex__item10 { --span: 10;}.flex__item11 { --span: 11;}.flex__item12 { --span: 12;}
在该示例中采用了CSS自定义属性相关的特性,让整个计算变得更容易一些。
<!-- HTML --><grid__container> <grid__item></grid__item></grid__container>
- 使用 fr 将网格均分为相等的值,即每列宽度都是 1 个 fr;配合 repeat() 函数,即repeat(12, 1fr) 创建了12列网格。
- 使用 gap 可以用来控制网格之间的间距。
- 配合 minmax() 还可以设置网格最小值。
:root { --columns: 12; --gap: 10px; --span: 1;}.grid__container { display: grid; grid-template-columns: repeat(var(--columns), 1fr); grid-template-rows: 1fr; gap: var(--gap); padding-left: calc(var(--gap) / 2); padding-right: calc(var(--gap) / 2);}.grid__item { min-block-size: 10vh; grid-column: span var(--span);}.col1 { --span: 1;}.col2 { --span: 2;}.col3 { --span: 3;}.col4 { --span: 4;}.col5 { --span: 5;}.col6 { --span: 6;}.col7 { --span: 7;}.col8 { --span: 8;}.col9 { --span: 9;}.col10 { --span: 10;}.col11 { --span: 11;}.col12 { --span: 12;}
就该示例而言,grid-template-columns: repeat(12, 1fr) 创建网格如下图所示:
除了上述这种粗暴的方式,还可以更灵活一些,将 auto-fit、minmax() 以及 grid-auto-flow: dense 等来创建:
.grid__container { padding: 1em; display: grid; grid-template-columns: repeat(auto-fit, minmax(60px, 1fr)); gap: 1em; grid-auto-flow: dense;}
对于 .grid__item 可以通过 grid-column、grid-row 来控制网格项目的位置:
加上 grid-auto-flow: dense 会根据Grid容器空间,Grid项目会自动流到合适的位置:
在Web布局中时常碰到两端对齐的需求。在Flexbox布局中,时常在Flex容器中显式设置 justify-content 的值:
.flex__container { display: flex; flex-wrap: wrap; justify-content: space-between; width: 100%;}
但在末尾行,如果和前面行的个数不相同(Flex项目)就会出现下图这样的效果:
像上图这样的效果,并不是我们所需要的,因为我们希望在最后一行的Flex项目不足够排列满一行时,希望Flex项目一个紧挨一个的排列:
在Flexbox要实现上图这样的效果,只需要在Flex容器中添加一个伪元素:
.flex__container::after { content: ""; display: flex; flex: 0 1 32vw;}
注意,伪元素的 flex-basis 建议设置的和卡片的 flex-basis(或宽度)等同。这个时候你将看到像下面这样的示例:
不过这种方式也不是最佳的方式,当末尾行的个数不只少一个时,就会出现下图这样的效果:
面对这样的场景,我们需要给Flex容器添加额外的空标签元素:
body { padding: 1vh;}.flex__container { display: flex; flex-wrap: wrap; gap: 2vh; width: 100%;}.flex__item { flex: 0 1 calc((100vw - 8vh) / 4);}
注意,gap 运用在Flexbox中到目前为止,仅得到了Firefox浏览器的支持。上面的示例,使用Firefox浏览器,你看到的效果如下:
body { padding: 1vh;}.grid__container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1vh;}
很多时候,针对不同的场景,设计师会为我们提供不同的设计风格,比如元素大小:
clam() 函数接受三个参数,即 clam(MIN, VAL, MAX),其中 MIN 表示最小值,VAL 表示首选值,MAX 表示最大值。它们之间:
- 如果 VAL 在 MIN 和 MAX 之间,则使用 VAL 作为函数的返回值。
- 如果 VAL 大于 MAX,则使用 MAX 作为函数的返回值。
- 如果 VAL 小于 MIN,则使用 MIN 作为函数的返回值。
.element { /** * MIN = 100px * VAL = 50vw ➜ 根据视窗的宽度计算 * MAX = 500px **/ width: clamp(100px, 50vw, 500px);}
比如浏览器视窗现在所处的位置是1200px的宽度,那么 .element 渲染的结果如下:
就该示例而言,clamp(100px, 50vw, 500px) 还可以这样来理解:
- 元素 .element 的宽度不会小于 100px(有点类似于元素设置了 min-width: 100px)。
- 元素 .element 的宽度不会大于 500px(有点类似于元素设置了 max-width: 500px)。
- 首选值 VAL 为 50vw,只有当视窗的宽度大于 200px 且小于 1000px 时才会有效,即元素.element 的宽度为 50vw(有点类似于元素设置了 width:50vw)。
正像上图所示,Logo图像的有大有小(宽度和高度都不一样)。面对这样的业务场景,很多时候都希望设计师能提供相同尺寸的图像。但这样势必会影响Logo图像的外观。
前段时间看到@Ahmad Shadeed专门写了一篇博文《Aligning Logo Images in CSS》,就是介绍如何实现上图这样的布局效果。
其实实现这样的布局效果,主要运用到的就是CSS的 object-fit 属性,而这个属性早在多年前就得到了各大主流浏览器的支持。
这里我们用一个简单的示例,来看看具体实现过程。先来看HTML结构:
<!-- HTML --><ul class="brands"> <li class="brands__item"> <a href="#"> <img src="img/logo.png" alt=""> </a> </li> <li> <!-- ... --> </li></ul>
.brands { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-gap: 1rem;}.brands__item { background: #eee;}.brands__item a { display: flex; justify-content: center; align-items: center; height: 100%;}.brands__item img { width: 130px; height: 75px; object-fit: contain;}
这样就能实现上图的效果。你可能发现了,有些Logo图像带有背景颜色,如果让效果更好一些,可以把CSS混合模式相关的特性运用进来:
.brands__item img { width: 130px; height: 75px; object-fit: contain; mix-blend-mode: multiply;}