十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CSS Grid实战:从基础到生产环境避坑指南

CSS Grid实战:从基础到生产环境避坑指南 先说明一下范围。Grid这个词在技术圈里被用得相当泛滥PCB有pcb布局布线操作系统有磁盘分区布局JVM有对象内存布局Android有协调布局甚至Type-C引脚都有布局规范。但搜索量和讨论热度最高的还是前端CSS里的Grid布局。我下面聊的就是前端页面里的display: grid这套东西。这两年我用Grid重构了三个后台管理系统、两个大屏看板和若干门户首页最深的感受是网上讲Grid的文章十有八九停留在“给你背一遍属性”跟着抄完觉得自己会了真到了项目里要做个“左侧导航固定、右侧主体自适、底部横跨整行”的界面又开始怀疑人生。所以这篇不打算把Grid所有属性按文档念一遍而是按我实际项目里的思考路径来写什么时候该上Grid、轨道尺寸怎么定、元素怎么放、响应式怎么自动排、生产环境会遇到什么莫名其妙的问题、以及我打开DevTools是怎么一步步排查的。1. Grid和Flexbox不是选择题是两种不同的分工1.1 Flexbox解决的是“一条线上怎么排”Flexbox的全称是Flexible Box它从设计之初要解决的核心问题是“在一根轴线上如何分配空间”。主轴是横向就横着排主轴是纵向就竖着排配合flex-wrap: wrap可以换行但换行后第二行和第一行之间没有任何“列对应关系”。我举个例子你就明白了一个flex容器里放了六张卡片宽度不够时换行第二行的卡片会被单独拉伸或收缩你没办法让第二行的第一张卡片恰好和第一行第一张卡片对齐成整齐的两列。想强行对齐只能手动计算宽度百分比或者给每个缺口补margin非常痛苦。Flexbox擅长的是处理一串内容相近、排列方向明确的子项比如导航菜单、按钮组、标签集合、单行图标列表。1.2 Grid解决的是“二维平面上怎么切”Grid布局引入了一个核心概念网格线。你先定义容器里有几条竖线、几条横线这些线交叉形成坐标系然后把子元素扔进坐标系。每个子元素不仅知道自己占了几列还知道自己从第几行开始、到第几行结束。这就是二维控制也是Grid和Flexbox最本质的区别。Grid真正的主场是“需要行列联动的复杂结构”后台管理的整体页面框架、大屏可视化面板、图文混排的卡片墙、商品列表这种既要等宽又要等高还要支持跨行跨列的场景。用Flexbox写这些需要嵌套三四层容器加各种hack用Grid一个display: grid加几行模板就能搞定。1.3 实际选型标准别把“二维论”当教条很多人喜欢把“一维用Flex二维用Grid”当成金科玉律实际操作里其实有例外。比如一个水平导航栏即使它会换行Flex仍然更合适因为换行后的子项之间不需要严格对齐。反过来一个规整的图片墙表面看是多行多列但如果你没有任何跨行跨列需求Flex配合flex-wrap也能凑合。我自己做判断的标准只有一条当你发现某个子元素“既要管行方向的位置、又要管列方向的位置”或者多处元素需要共享同一组轨道边界时必须上Grid如果只是在处理一串同质内容的排列包装用Flex更省心。日常项目里我从来不搞二选一而是一个页面里混合用外层用Grid切大骨架骨架内部的某个单元格里再用Flex排横向的小组件。可以用一个表格来总结这几年的使用感受对比维度FlexboxGrid维度一维主轴/交叉轴二维行/列同时控制换行对齐换行后各行独立不对齐轨道天然对齐跨行跨列需要嵌套计算原生支持适合场景导航、按钮组、列表包装页面框架、卡片墙、复杂区块上手成本较低略高但值得2. 从零搭一个Griddisplay、template、gap三件套2.1 最小可用的网格只需要两行CSS启动一个Grid布局最低限度两行.container { display: grid; grid-template-columns: 200px 1fr 200px; }第一行把容器切换到网格模式第二行定义了三条竖线围成的三列左右固定200px中间自动占满剩余空间。这个组合在后台系统里出现频率极高因为大多数内容型页面都长这样左右侧栏固定宽中间主区域自适应。有一个常见误解以为写了display: grid子元素就会自动变成什么神奇形态。其实即使不写任何grid-template-*子元素也只是在单列里自上而下排列看起来跟普通块级元素差不多。真正决定布局的是轨道模板display: grid只是把坐标系建立起来而已。div classcontainer aside侧栏/aside main主体/main div classextra右侧栏/div /div上面这段HTML配合前面的CSS出来就是一个标准的“左中右三栏”不需要任何浮动不需要flex-wrap也不用担心高度撑不开的问题。2.2 fr单位、百分比、auto三种宽度逻辑grid-template-columns的值可以写fr、百分比、px、auto甚至min-content、max-content。关键是得理解fr到底是什么它是剩余空间分配单位不是绝对宽度单位。假设容器总宽1200px定义三列为1fr 2fr 1fr浏览器会先把固定占位如果有px或者其他内容撑开的区域扣掉然后把剩余空间按1:2:1的比例分给三列。写百分比是按容器总宽直接算比例但如果子项有padding或border百分比容易溢出。写auto则是由内容决定列宽内容多就宽内容少就窄常用于“标签宽度不定”的场景。我项目里经常用这样的组合.search-grid { display: grid; grid-template-columns: auto 1fr 1fr; gap: 12px; }第一列跟着内容走适合放“关键字”之类的标签后面两列自动平分剩余空间适合放输入框和按钮。这种写法在搜索筛选表单里非常好用不用为标签长度不同而头疼。2.3 minmax()是防止挤压和爆发的双保险minmax(min, max)是我在所有Grid属性里使用频率最高的函数。它让一个轨道在最小值和最大值之间浮动尤其适合做响应式卡片墙.card-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }意思是每列最少240px最多可以占1fr尽量宽浏览器根据容器当前宽度自动决定一排放几个。这是H5响应式布局里最简单粗暴的写法能省掉成堆的媒体查询。minmax有两个特殊值值得单独说minmax(0, 1fr)和minmax(auto, 1fr)。后者其实是很多情况的默认行为也就是说1fr对应的最小值并不一定是0而是auto内容的最小尺寸。所以当某个列里放了超长英文链接或大表格时这列会被内容撑开其他列被挤扁。明确写成minmax(0, 1fr)轨道才会老老实实从0开始分配。2.4 gap比margin靠谱一百倍gap在Grid里表示行与行、列与列之间的间距。用gap最大的好处是没有margin合并问题也不会给两端多出额外间距所有间距都存在于格子之间是真正意义上的“内部呼吸空间”。有了gap之后以前用margin负值做的各种花式hack基本可以淘汰了。但要注意如果你在子项上同时写了marginmargin会和gap叠加视觉间距会变大。我给自己定的规矩是一个网格体系内间距方案只能选一种要么全用gap要么全用margin绝不混用。3. 元素入格精确放置、区域命名与重叠3.1 用grid-column和grid-row指定位置轨道定义好之后容器就有了一个带编号的坐标系行号和列号都从1开始。子元素默认按DOM顺序自动放进单元格这叫自动放置。想要精确控制某个子元素的位置可以给子项写grid-column和grid-row.item { grid-column: 2 / 4; grid-row: 1 / 3; }意思是这个元素从第2条竖线开始到第4条竖线结束从第1条横线开始到第3条横线结束。它最终占了两列两行。更常用的一个写法是.item { grid-column: 1 / -1; }-1代表最后一条网格线所以这个元素从第一条线跨到最后一条线等于“横跨整行”。标题、错误提示、底部分隔线我经常用它来实现。如果想表达“占两行”又不关心具体从哪行开始可以写grid-row: span 2。span关键字不需要知道行号它描述的是“我这个元素自己跨几行”符合直觉代码也更好维护。3.2 grid-template-areas把布局写成ASCII图这是我最喜欢的Grid功能没有之一。它允许你给区域起名字然后在子项上直接用名字指定位置.layout { display: grid; grid-template-columns: 260px 1fr; grid-template-rows: 64px 1fr 48px; grid-template-areas: header header sidebar main sidebar footer; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .main { grid-area: main; } .footer { grid-area: footer; }代码一贴出来布局结构一目了然。比去HTML里翻class名字高效太多团队协作时尤其香新同事看一遍grid-template-areas的值就能知道页面骨架是什么样。容易被坑的点grid-template-areas的每一个字符串代表一行几个字符串之间列数必须严格对应grid-template-columns定义的列数。如果你定义了三列那每个字符串里必须出现三个区域名区域名可以重复也可以用.表示空位。列数对不上时整个grid-template-areas会静默失效子元素回到自动排列状态浏览器不报错只能自己排查。我排查的时候第一步就是数列数。3.3 重叠不是Bug是高级布局手段Grid允许子元素被放在同一个单元格里后者会覆盖在前者上面。默认按DOM顺序叠放也可以用z-index控制层级。很多视觉需求其实都是“重叠”图片上的角标、标题文字压在色块上、大屏看板里数字浮在背景图上。用Grid做这些比用绝对定位干净得多你不需要给父容器设置position: relative也不用慢慢调left/top只要让两个元素占据相同的grid-area就行.banner { display: grid; grid-template-areas: content; } .banner-item { grid-area: content; } .banner-text { grid-area: content; align-self: end; z-index: 1; }这里的grid-template-areas: content只定义了一个单元格两个子元素都放在这个格子里一个做背景一个做前景文字再用align-self: end把文字按到格子底部。注意要给底层元素设置position: relative配合z-index否则某些浏览器里背景色或图片容易把文字盖住这也是布局重叠里最常见的翻车原因。4. 响应式网格auto-fit、auto-fill和隐式轨道4.1 repeat(auto-fit, minmax(...))移动端最实用的响应式方案以前做响应式卡片墙得写一堆媒体查询media (min-width: 768px) { .list { grid-template-columns: repeat(2, 1fr); } } media (min-width: 1024px) { .list { grid-template-columns: repeat(3, 1fr); } }用Grid可以直接把这件事交给浏览器自己算.card-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }容器宽浏览器多放几列容器窄少放几列实在放不下就退化成单列。整个卡片墙是真正的流式自适应不需要任何断点代码。这在H5响应式页面里非常实用数据量多的时候也不会撑破布局。4.2 auto-fit与auto-fill的细微差别很多人从repeat(auto-fit, minmax(...))这段代码入门但没注意到auto-fit和auto-fill其实是两个不同的关键字。就一个例子看懂容器宽度600px列最小宽度200px。auto-fill会算出最多能放3列当只有2个子元素时它依然保留3列的空间第三个是空轨道auto-fit同样能算出3列但会把多余的空轨道折叠成0让现有的2个子元素占据完整600px宽度。结论很直接做卡片墙、内容列表一般用auto-fit因为你不想内容旁边留一片空白做“时间线”“有规律的占位结构”“日历格子”这种需要保持固定位置对应关系的场景用auto-fill更合适。4.3 隐式轨道和grid-auto-flow当子元素数量超过显式定义的轨道时浏览器会自动生成新的行或列来容纳剩余元素这些自动生成的轨道叫隐式轨道。隐式轨道默认高度是auto即由内容决定。想让隐式行等高可以用grid-auto-rows统一控制.grid { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 80px; }这个写法在大屏UI里出现频率特别高。大屏看板通常需要等高的行块固定行高能避免数据加载前后高度跳动导致的对不齐。我在调试大屏UI排版布局时第一件事就是把grid-auto-rows设死等样式稳定再回来调。grid-auto-flow控制自动放置的方向默认值是row按行排排满一行换下一行改成column就先按列排。关键字dense表示“密集”填充.grid { grid-auto-flow: row dense; }dense会让浏览器回头把空隙用后面的元素填上适合不同高度的标签云、瀑布流卡片。但要记住dense会改变元素的视觉顺序在纯展示场景没问题在表单或阅读顺序严格的场景请慎用否则键盘Tab顺序和视觉顺序不一致很容易得罪做无障碍的朋友。4.4 移动端优先时网格怎么定我的习惯是这样移动端先把所有重要内容按阅读顺序排成单列桌面端再用媒体查询切成多列多行。由于Grid的areas可以只改模板不换DOM这个切换体验可以说是全前端最爽的移动端.detail { display: grid; grid-template-areas: title gallery params buy; }桌面端media (min-width: 1024px) { .detail { grid-template-columns: 1fr 1fr; grid-template-areas: gallery title gallery params buy buy; } }同一个DOM两套模板桌面端左侧大图右侧参数购买按钮横跨整行。媒体查询里也不需要重复设置display: grid只需要改columns和areas省心程度远超纯flex方案。5. 对齐justify和align在Grid里到底管谁5.1 items管的是“单元格内部”Grid里同样有justify-items、align-items但它们不像Flex那样分主次轴因为Grid是二维的没有主轴交叉轴的概念只有行方向和列方向。我的记忆锚点是justify管水平方向对应列方向align管垂直方向对应行方向所以justify-items控制子元素在单元格内的水平对齐align-items控制子元素在单元格内的垂直对齐。默认值都是stretch子元素会填满整个单元格。如果给单元格里放一个按钮不想让它被拉伸成通栏就把justify-items: start或者单独给子项写justify-self。5.2 content管的是“整个网格的位置”当容器比网格整体尺寸大时比如容器有明确高度或者列宽总和小于容器宽度justify-content和align-content就会生效。它们控制的是整个网格在容器里的位置而不是某个格子内部。.grid { display: grid; grid-template-columns: 200px 200px 200px; justify-content: center; align-content: center; height: 100vh; }三列在水平方向居中整个网格在垂直方向也居中。做居中页面、加载提示、登录卡片这类场景时这个组合比flex的margin: auto大法直观得多。这里有个隐藏条件align-content: center要生效容器必须有明确的高度否则容器高度被内容撑开没有剩余空间可分配属性写了等于没写。我在项目里见过不少同学在这行代码上折腾半小时其实只是忘了给父容器设置height: 100vh。5.3 用一张表记住Grid和Flexbox的对齐差异写法Grid中的作用Flexbox中的作用justify-items子元素在单元格内的水平对齐无对应align-items子元素在单元格内的垂直对齐交叉轴对齐justify-content整个网格在容器内的水平位置主轴对齐align-content整个网格在容器内的垂直位置交叉轴的多行分配justify-self单个子元素在单元格内的水平对齐无对应可用margin:auto代替align-self单个子元素在单元格内的垂直对齐交叉轴单元素对齐只要记住“justify是水平列方向align是垂直行方向”Grid里就基本不会写错。万一乱了打开DevTools看网格覆盖层哪里没对齐一眼就能看到。6. 生产环境最常见的几个坑以及我的排查方法6.1 min-width:auto导致内容撑破轨道这个坑我几乎每周都会遇到。grid子项默认的min-width是auto意思是“我的宽度不小于内容的最小宽度”。当子项里放的是长URL、大表格、或者一连串无空格英文字母时内容会把轨道撑破导致整排格子右侧溢出。解决办法是给子项加min-width: 0.grid-item { min-width: 0; }这看起来就一行CSS不理解原理的话永远记不住什么时候该加。我现在写Grid模板的时候几乎给每个会放动态内容的子项都预设min-width: 0数据多了也不怕。和它对称的问题是行方向的grid-template-rows: 1fr的子项如果内部内容太高会把行高撑大想让这些内容在固定区域内滚动必须给子项加min-height: 0。这两兄弟是Grid生产环境里最顽固的两个坑90%的“布局一团乱”都是它们引起的。6.2 1fr不等于“平均分”很多人以为1fr 1fr 1fr就是三等分其实浏览器会先分配auto和px轨道的空间再把剩余空间给fr轨道。如果某个列的内容最小宽度非常大那个fr轨道会被内容自动撑大其他列被迫缩水。避免意外的方法就是在关键列上使用minmax(0, 1fr).grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }这样每一列的最小值都是0浏览器分配剩余空间时不会被内容绑架才能真正做到三等分。6.3 用DevTools的Grid Overlay来排查浏览器DevTools现在的Grid调试能力很强选中带display: grid的容器后Elements面板里会显示一个网格小图标点一下就能看到网格覆盖层所有轨道线、间隙区域、每个子项的放置位置都会用彩色虚线画出来。排查布局重叠问题时覆盖层价值最大你能直接看到哪些元素被放进了同一个单元格以及它们的实际尺寸。Layout面板里还可以临时改轨道宽度和间隙大小实时看到效果不用反复刷新页面。我调试大屏看板和对齐问题全程都在这个面板里改数改完再同步到代码效率比来回盲改高很多。6.4 和Vue、React框架配合时容易踩的坑动态渲染导致布局出问题的场景我遇到过很多次典型的表现是某列表项在数据加载前是空的加载后有了实际宽度触发了Grid列之间的重新分配整个布局跳了一下。解决办法是用grid-auto-rows或minmax把隐式轨道的尺寸提前定死数据进来后高度不会突变。另一个跟框架相关的问题是条件渲染用v-if或动态组件控制某个子项显示隐藏时Grid会自动把剩下的子项重新排列如果你的设计稿要求“某个格子即使内容为空也要占位”那不要用条件渲染销毁DOM用一个固定的grid-area占位元素更稳。6.5 媒体查询切换时记得同步重置模板最后分享一个我自己翻过车的细节一个商城详情页桌面端是左图右信息底部跨栏按钮移动端改成单列。我在移动端媒体查询里只重置了grid-template-areas忘了同步grid-template-columns结果区域名定义了三列字符串里却只有两个区域整个areas静默失效元素全按自动顺序乱排页面直接“垮掉”。检查这类问题其实很快打开DevTools看看容器上是不是有条样线提醒你“值无效”没有的话就检查areas字符串的列数和columns的列数是否一致。媒体查询里的每次改动我现在的习惯是把grid-template-columns和grid-template-areas看成一对要改就一起改不做单独操作。就我个人经验来说Grid布局真正上手的门槛不在语法而在思维转换你得从“用嵌套结构凑布局”切换到“先定义轨道再思考每个元素属于哪个格子”。一旦过了这道坎以前那些让代码变得又臭又长的布局hack会大量消失。
返回列表