Flex 与 Grid 布局生成

拖动参数实时预览 Flexbox 与 CSS Grid 布局,同时把每个子项算出来的最终像素尺寸、剩余空间怎么分配列出来,CSS 与 HTML 一起复制就能用。

自定义属性、class 命名风格不改动,只把选择器前缀换成你填的名字,CSS 与下面的 HTML 用的是同一个类名。

使用说明

Flexbox 的参数按轴来分:flex-direction 定下主轴方向,justify-content 管主轴上的对齐,align-items 管交叉轴上的对齐,align-content 管多行之间怎么分布——注意它只在开了 wrap 并且真的换成两行以上时才有意义,单行时写它等于没写,所以页面在 nowrap 状态下会直接把这一项禁用掉。gap 分列间距和行间距两档,只在项目之间留空,首尾不留,这是它比负 margin 老写法干净的地方。预览容器的宽高既是画面尺寸,也是算剩余空间的依据,写死它们是刻意的:子项分到多少空间完全取决于容器有多大,尺寸一变数字就该重算。

子项的 flex-basis 是分配开始前的初始尺寸,不等于最终宽度,这一步最容易误解。算法分三步:先按所有子项的 basis 加上间距判断这一行放不放得下,放不下就按 flex-wrap 换行(nowrap 时全挤在一行、直接溢出);然后算这一行的剩余空间,正剩余按 flex-grow 的比例分摊,负剩余按「flex-shrink 乘 flex-basis」的比例从各项收回去。所以 basis 越大,收缩时承担的份额越多,一个 200px 的项会比一个 100px 的项多收缩一倍的量。页面上把每一行的剩余空间和每个子项最终的像素值都算出来并列在预览区下面,改一个滑块就能看到 basis 和最终值的差距在哪里。

还有一个绕不开的坑:flex 子项的自动最小尺寸是 min-width: auto,等于它内容的最小尺寸。也就是说,即使 flex-shrink 是 1,只要内容里有长单词、不换行的表格或者一张不能缩的图,子项就缩不下去,直接把容器撑破,而 flex-shrink 看上去「失效」了。纵向布局时对应的是 min-height: auto。解决办法是给子项补上 min-width: 0 把自动最小尺寸归零,这也是本页默认帮你在子项声明里带上它的原因;取消那个勾就能看到容器被撑破、页面上报出 overflowing 行号的效果。同样的道理,横向滚动条突然出现的时候,先检查有没有该归零的自动最小尺寸。

Flexbox 是一维的,Grid 是二维的,这是两者最根本的区别,也是选型的判断依据。Flexbox 只把子项排在一条主轴线上,换行后每一行独立分配空间,行与行之间互不相干,所以一排按钮、一条导航、一组卡片容器用它最顺手;Grid 同时定义行和列,子项可以跨行跨列、可以指定落在哪个格子,页头页脚通栏加中间三栏这类结构用 Flexbox 得嵌套两层才对得上,用 Grid 一行 grid-template-areas 就够了。列轨道支持 fr(按比例分剩下的空间)、px、百分比、auto(跟着内容走)、minmax(最小, 最大) 和 repeat(n, 1fr) 这几种写法,可以混着用,比如侧栏固定 240px、正文 1fr 就是最常用的两列结构。卡片网格用 repeat(auto-fill, minmax(160px, 1fr)) 能免掉媒体查询:列数由容器宽度自己决定,会自动增减;如果希望卡片少的时候把它们拉宽填满,把 auto-fill 换成 auto-fit。

命名区域的写法是每行一个带引号的字符串,行与行之间用空格分隔,同一个名字出现多次就表示这个区域跨了那些格子,用点号占住不放内容的格子。规范要求所有行的轨道数一致,短一行不会自动补齐,不一致时浏览器会把整条 grid-template-areas 丢掉,所以页面上直接做了校验并指出是第几行出了问题。要一个 2 乘 2 的大图就把它四个格子都写成同一个名字;用了命名区域之后自动排布规则会让位,grid-auto-flow 也就没意义了,页面会把它一并禁用。所有生成的数值都是按规范里的算法算出来的,预览区用的就是即将复制的那份 CSS,两者同源;唯一算不出来的东西是文字内容的固有最小宽度,所以这一页按 min-width: 0 来计算,这也是标签页里勾选默认打开的前提。想给页面配个渐变背景可以去CSS 渐变生成,需要占位图填格子用占位图生成,配色不确定时先在颜色转换里把色值定下来。

常见问题

Flexbox 和 Grid 到底该用哪个?

最根本的区别是 Flexbox 是一维的、Grid 是二维的。Flexbox 把子项排在一条主轴线上,换行之后的每一行各自独立分配空间,行与行之间没有关系,所以适合一排按钮、一条导航、一个卡片容器这种「一条线上的排布」。Grid 能同时控制行和列,子项可以跨行跨列、可以对齐到指定的格子,所以页头页脚通栏加中间三栏、图片墙这种结构要用它。判断方法很简单:如果你在描述里说了「第几行第几列」或者需要两个方向同时对齐,就该用 Grid。

相关:CSS 渐变生成

我写了 flex-basis: 200px,为什么实际渲染出来只有 173px?

因为 basis 只是分配开始前的初始尺寸,不是最终宽度。容器放不下时,多出来的部分按「flex-shrink 乘 flex-basis」的比例从各个子项身上收回去,basis 越大承担的收缩量越多;容器有富余时才按 flex-grow 的比例往上加。所以 200px 被压到 173px 是正常结果。这一页把每个子项算出来的最终像素值直接列出来了,一边调一边就能看到 basis 和最终值差多少。

为什么子项明明放不下却不肯缩小,直接把父容器撑破了?

这是 flex 子项默认 min-width: auto 的经典坑。子项的自动最小尺寸等于它内容的最小尺寸,一段长文本、一个不换行的表格都算,只要内容撑得住,flex-shrink 就动不了它。解决办法是给子项补上 min-width: 0(纵向布局是 min-height: 0),把自动最小尺寸归零,收缩才能真正生效。这一页默认帮你在子项声明里带上 min-width: 0,去掉它对勾就能看到容器被撑破、算出 overflowing 的效果。

justify-content 和 align-items 有什么区别?

justify-content 管的是主轴,align-items 管的是交叉轴,两条轴哪条是主轴由 flex-direction 决定。direction 是 row 时主轴水平,justify-content 控制左右、align-items 控制上下;改成 column 之后两者的作用方向整个对调过来,这是最容易搞混的地方。另外还有 align-content,它管的是「多行之间」的间距,只在开了 wrap 并且真的换成两行以上时才有效,单行时写它没有任何效果。

gap 和以前用负 margin 凑间距的老写法有什么区别?

老写法靠给容器加负 margin、再给每个子项加正 margin 把多出来的那一圈抵消掉,副作用是容器的实际尺寸被撑大过、和父元素的边距对不上,而且在列表首尾那一圈还得单独处理。gap 是布局本身提供的间距,只在项目之间留空、首尾不留,也不会改变容器的尺寸计算,换行时行间距由 row-gap 单独控制。现在主流浏览器都支持 flex 和 grid 上的 gap,新代码没有理由再写负 margin 那一套。

grid-template-areas 每行的格子数不一样会怎样?

整条声明会被浏览器丢弃,等于没写。规范要求所有行的轨道数一致,行与行之间用空格分隔、每一行是一个带引号的字符串,短的那一行不会自动补齐。这一页会在输入区域下方直接报出是哪一行的轨道数对不上;要留空位就写一个点号占位,比如用点号占住那个格子,占位符本身不会生成同名区域。

卡片网格用 repeat(auto-fill, minmax(160px, 1fr)) 就不用写媒体查询了吗?

基本不用。auto-fill 让浏览器按容器实际宽度自己决定能塞几列,minmax(160px, 1fr) 给每列设了最小 160px、最大等分剩余空间,两边一配合,列数会跟着容器宽度自动增减,不需要断点。要注意 auto-fill 会保留空轨道(列数按容器的空间算),auto-fit 则会把没内容的空轨道折叠掉;两者在内容少时会呈现不同的宽度表现,选哪个取决于你希望少几张卡片时它们是否被拉宽。

相关:占位图生成

这一页算出来的像素值和浏览器渲染出来的真的一样吗?

一样,前提是子项的自动最小尺寸真的被归零了,也就是父容器带上 min-width: 0、子项也带上 min-width: 0 时成立。算法和规范一致:先按 flex-basis 加间距判断能不能放得下并分行,然后正剩余按 flex-grow 的比例分摊、负剩余按「flex-shrink 乘 flex-basis」的比例收回,纵向布局时把主轴换成高度。差别只在文字内容的最小尺寸上——浏览器知道一段文字的固有宽度,这里不知道,所以默认按 min-width: 0 计算,预览区用的就是这份数字对应的 CSS。