当前期刊数: 124
1 引言 Flex 与 Grid 相比就像功能键盘和触摸屏。触摸屏的控制力相比功能键盘来说就像是降维打击,因为功能键盘只能上下左右控制(x、y 轴),而触摸屏打破了布局障碍,直接从(z 轴)触达,这样 无论 UI 内部布局再复杂,都可以通过 touch 直接定位。
Flex 是一维布局方式,我们需要不断嵌套 Div 才能形成复杂结构,而一旦布局产生了变化,原有嵌套结构如果不能 “兼容变化” 到新结构,代码就需要重构。而 Grid 就像触摸屏一样,可以二维布局,即便布局方式做了翻天覆地的调整,也仅需少量修改就能适配。
这就是这次精读 用 css grid 重新思考布局 的原因,理解这个革命性布局技术给布局,甚至代码逻辑组织带来的变化。
2 概述 作者首先抛出了 Flex 的问题,其实是 block
float
flex
这三种布局模式的通病:
布局结构由 Div 层级结构描述,导致 Div 层级复杂且遇到结构变更时难以维护。
定制能力弱。Flex 布局有一些不受控制的智能设定,比如宽度 50% 的子元素会被同级元素挤到 50% 以下,这种智能化在某些场景是需要的,但由于没有提供像 Grid 的 minmax
之类的 API,所以定制型不足。
举个例子,上图的结构用 Flex 描述可能是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <div class ="card" > <div class ="profile-sidebar" > <img src ="https://i.pravatar.cc/125?image=3" alt ="" class ="profile-img" /> <ul class ="social-list" > <li > <a href ="##" class ="social-link" > <i class ="fab fa-dribbble-square" > </i ></a > </li > <li > <a href ="##" class ="social-link" > <i class ="fab fa-facebook-square" > </i ></a > </li > <li > <a href ="##" class ="social-link" > <i class ="fab fa-twitter-square" > </i ></a > </li > </ul > </div > <div class ="profile-body" > <h2 class ="profile-name" > Ramsey Harper</h2 > <p class ="profile-position" > Graphic Designer</p > <p class ="profile-info" > Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere a tempore, dignissimos odit accusantium repellat quidem, sit molestias dolorum placeat quas debitis ipsum esse rerum? </p > </div > </div >
利用 HTML 嵌套结构,我们将图形纵向分成两大块,然后在每块内部继续嵌套划分布局,这是最经典的布局行为了。
样式文件里,我们需要对每层布局进行描述,同时支持多分辨率弹性布局,包括顶层 card
容器在内的一些样式需要做一定调整:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 .card { width : 80% ; margin : 0 auto; display : flex; flex-direction : column; max-width : 600px ; background : ##005e9b ; flex-basis : 250px ; color : white; padding : 2em ; text-align : center; }.profile-info { font-weight : 300 ; opacity : 0.7 ; }.profile-sidebar { margin-right : 2em ; text-align : center; }.profile-name { letter-spacing : 1px ; font-size : 2rem ; margin : 0.75em 0 0 ; line-height : 1 ; }.profile-name ::after { content : "" ; display : block; width : 2em ; height : 1px ; background : ##5bcbf0 ; margin : 0.5em auto 0.65em ; opacity : 0.25 ; }.profile-position { text-transform : uppercase; font-size : 0.875rem ; letter-spacing : 3px ; margin : 0 0 2em ; line-height : 1 ; color : ##5bcbf0 ; }.profile-img { max-width : 100% ; border-radius : 50% ; border : 2px solid white; }.social-list { list-style : none; justify-content : space-evenly; display : flex; min-width : 125px ; max-width : 175px ; margin : 0 auto; padding : 0 ; }.social-link { color : ##5bcbf0 ; opacity : 0.5 ; }.social-link :hover ,.social-link :focus { opacity : 1 ; }.bio { padding : 2em ; display : flex; flex-direction : column; justify-content : center; }@media (min-width : 450px ) { .bio { text-align : left; max-width : 350px ; } }.bio-title { color : ##0090d1 ; font-size : 1.25rem ; letter-spacing : 1px ; text-transform : uppercase; line-height : 1 ; margin : 0 ; }.bio-body { color : ##555 ; }.profile { display : flex; align-items : flex-start; }@media (min-width : 450px ) { .card { flex-direction : row; text-align : left; } .profile-name ::after { margin-left : 0 ; } }
让我们看看 Grid 是怎么做的吧!Grid 有许多 API,我们重点看 grid-template-areas
这个属性,利用它,我们可以不关心模块的 HTML 结构,直接平铺方式描述:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <div class ="card" > <img src ="https://i.pravatar.cc/125?image=3" alt ="" class ="profile-img" /> <ul class ="social-list" > <li > <a href ="##" class ="social-link" > <i class ="fab fa-dribbble-square" > </i > </a > </li > <li > <a href ="##" class ="social-link" > <i class ="fab fa-facebook-square" > </i > </a > </li > <li > <a href ="##" class ="social-link" > <i class ="fab fa-twitter-square" > </i > </a > </li > </ul > <h2 class ="profile-name" > Ramsey Harper</h2 > <p class ="profile-position" > Graphic Designer</p > <p class ="profile-info" > Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere a tempore, dignissimos odit accusantium repellat quidem, sit molestias dolorum placeat quas debitis ipsum esse rerum? </p > </div >
可以看到,使用 Grid 可以将 UI 结构与 HTML 结构分离,HTML 结构仅描述包含关系,我们只需在样式文件中描述具体 UI 结构。
样式文件只截取 Grid 相关部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 .card { width : 80% ; margin : 0 auto; display : flex; flex-direction : column; max-width : 600px ; background : ##005e9b ; flex-basis : 250px ; color : white; padding : 2em ; text-align : left; display : grid; grid-template-columns : 1 fr 3 fr; grid-column -gap : 2em ; grid-template-areas : "image name" "image position" "social description" ; }.profile-name { grid-area : name; }.profile-position { grid-area : position; }.profile-info { grid-area : description; }.profile-img { grid-area : image; }.social-list { grid-area : social; }
可以看到,grid-template-areas
是进一步抽象的语法,将页面结构通过直观的文本描述,无论是理解还是修改都更为轻松。
这种描述方式适配不同分辨率下也具有优势,只要重组 grid-template-areas
即可:
1 2 3 4 5 6 7 8 9 10 @media (min-width : 600px ) { .card { text-align : left; grid-template-columns : 1 fr 3 fr; grid-template-areas : "image name" "image position" "social description" ; } }
归根结底,Grid 通过二维结构描述,将子元素布局控制收到了父级,使布局描述更加直观。
最后作者也提到,Flex 依然有使用场景,即简单的一维结构,或者 space-between
等 Flex 独有语法的情况。因此推荐整体、复杂的二维布局采用 Grid,一维的简单布局采用 Flex。
3 精读 Grid 的布局思路给了我很多启发,HTML 结构与 UI 结构的分离有助于减少 DIV 的层级结构,使代码看上去更清晰。
也许有人会疑惑,Grid 无非将 HTML 布局部分功能挪到了 CSS,整体复杂度应该不变。其实,从 grid-template-areas
这个 API 可以看到,Grid 不仅仅将布局功能抽到 CSS 中,更是将布局描述进行了一层抽象,使代码更易维护。
抽象,再抽象 为什么 Grid 可以对布局进行抽象?因为 Grid 将二维结构都掌握在手中,得到了更大的布局能力,才能进一步将结构化语法抽象为字符串的描述。
抽象的好处是不言而喻的,你觉得一堆嵌套的 DIV 与下面的代码,哪个更易读呢?
1 2 3 4 5 6 .card { grid-template-areas : "image name" "image position" "social description" ; }
这就是抽象的好处,一般来说,代码抽象程度越高就越易读,越易维护。
再看一个 Chrome Grid 插件,将 Grid 可视化显示出来,并可以以 UI 方式进行调整:
UI 是对文本的再抽象,同时可以规避一些不可能存在的语法,比如:
1 2 3 4 5 6 .card { grid-template-areas : "image name" "image position" "social image" ; }
布局只能以凸多边形方式拓展,不可能分离,也不可能突然插入一个其他模块而变成凹多边形。因此 UI 可以将这个错误规避,并简化为横竖多条线的方式对 UI 进行划分,显然这种描述方式效率更高。
不得不说,Grid 以及图形化插件的探索,是布局领域的一大进步,是不断抽象的尝试,要解决的问题只有一个:如何提供一种更直观的描述 UI 的方式。
布局对模块化的影响 Grid 将布局方式提高了一个维度,会直接影响到 JS 模块化方式。
尤其是以 JSX 组织代码的情况下,一个模块等于 UI + JS,通过嵌套方式的布局会让我们更倾向于站在 UI 视角划分模块。
比如对于上图模块,如果用 Flex 方式布局,我们可能会首先创建模块 X 作为左侧容器,子元素是 A 和 B,创建模块 Y 作为右侧容器,子元素是 C 以及新容器 Z,Z 容器的子元素是 D 和 E。
如果你的第一印象是这么组织代码,不得不承认模块化会受到布局方式的影响。虽然许多时候这样划分是正确的,但当这 5 个模块各自没有关联时,我们创建的容器 X、Y、Z 就失去了复用性,在新的组合场景我们又要重新组合一遍。
但是在 Grid 语法中,我们不需要 X、Y、Z,只需要用 css grid generator 按照上图的方式拖拖拽拽即可自动生成如下布局代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 .parent { display : grid; grid-template-columns : 3 fr repeat (2 , 1 fr); grid-template-rows : repeat (5 , 1 fr); grid-column -gap : 0px ; grid-row -gap : 0px ; }.div1 { grid-area : 1 / 1 / 3 / 2 ; }.div2 { grid-area : 3 / 1 / 6 / 2 ; }.div3 { grid-area : 1 / 2 / 2 / 4 ; }.div4 { grid-area : 2 / 2 / 6 / 3 ; }.div5 { grid-area : 2 / 3 / 6 / 4 ; }
其实 grid-template-columns
grid-template-rows
组合起来使用比 grid-template-areas
更强大,但是纯代码方式描述没有 grid-template-areas
直观,可是配合一些可视化系统就非常直观了:
将 A ~ E 这 5 个模块布局抽出来后,它们之间的关系就打平了,我们可以完全从逻辑视角审视如何做模块化了。
4 总结 CSS Grid 本质上是一种二维布局的语法,相比 Block 、Flex 等一维布局方案,多了一个维度可以同时从行与列角度定义布局,因此派生出 grid-template-areas
等语法,整体上更内聚更直观,抽象度也更高了。
理解了这些也就理解了布局未来的发展方向,让布局与 Dom 分离 一直是前端的一个梦想,开发 UI 部分时,只需关心页面由哪些模块组成,去实现这些模块就行了,而不需要关心模块之间应该如何组合。在描述组合时,可以通过可视化或比较抽象的字符串描述布局的结构,并对应到写好的模块上,这样的代码维护性远高于用 DIV 描述结构的方案。
讨论地址是:精读《用 css grid 重新思考布局》 · Issue ##211 · dt-fe/weekly
如果你想参与讨论,请 点击这里 ,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证 )