开始之前
栅格系统,做过前端的同学对这个词应该是比较熟悉,作为90后的一批的前端程序员(甚至更早,伟哥也不不是太清楚,因为那个时候还没出生)学习做网站的时候就知道会用 bootstrap 了,当然了bootstrap里面应该是叫做网格系统。row, col-md-1, col-md-5,这些东西不知道有没有唤起过大家的一些回忆。网格系统的出现确实让我们做网站布局轻松了很多,网格系统就好比家里的地砖,把房子分成了一个一个的小格子,然后我们在上面随意摆放东西即可。三大框架(react angular vue)出来后。基于三大框架衍生出来的一些 UI 框架也有自己的栅格系统,像 ant-design 中深蓝栅格系统 <Row> <Col span={4}> </Row>
, element-ui 响应式栅格布局 <el-row><el-col :span=4></el-row>
都是栅格系统的代表。CSS3的新特性 Grid 同样是为了实现网格布局,这次就聊聊这个 Grid。本篇内容理论基础大部分是参考了阮一峰老师的教程,在撸代码的时候,加进去了一些自己疑问地方的,仿照移动端小米首页加入了实战的一些内容。
概述
网格布局(Grid)是最强大的CSS布局方案。
它将网页划分成了一个个网格可以任意组合不同的网格,作出各种各样的布局。

上图这样的布局,就是Grid布局的拿手好戏。
Grid布局与flex布局有一定的相似性。都可以指定容器内部多个项目的位置。但是他们也存在重啊区别。
Flex布局是轴线布局,只能指定“项目”针对轴线的位置,可以看作是 一维布局。Grid布局则是将容器分成“行”和“列”,产生单元格,然后指定“项目所在”的单元格,可以看作是“二维布局”。Grid布局远比Flex布局强大。
基本概念
学习 Grid 布局之前,需要了解一些基本概念。
容器和项目
采用网格布局的区域,称为“容器”(container)。容器内部采用网格定位的字元素,称为“项目”(item)。
<div>
<div><p>1</p></div>
<div><p>2</p></div>
<div><p>3</p></div>
</div>
上面代码中,最外层的 <div>
元素就是容器,内层的三个<div>
元素就是项目。
注意:项目只能是容器的顶层子元素,不包含项目的子元素,比如上面代码中<p>
元素就不是项目。Grid 布局只对项目生效。
行和列
容器里面水平区域称为“行”(row), 垂直区域称为"列"(column)。

上图中,水平的深色区域就是"行",垂直的深色区域就是"列"。
单元格
行和列的交叉区域,称为"单元格"(cell)。
正常情况下,n
行和m
列会产生n x m
个单元格。比如,3行3列会产生9个单元格。
网格线
划分网格的线,称为"网格线"(grid line)。水平网格线划分出行,垂直网格线划分出列。
正常情况下,n
行有n + 1
根水平网格线,m
列有m + 1
根垂直网格线,比如三行就有四根水平网格线。

上图是一个 4 x 4 的网格,共有5根水平网格线和5根垂直网格线。
容器属性
Grid 布局的属性分成两类。一类定义在容器上面,称为容器属性;另一类定义在项目上面,称为项目属性。这部分先介绍容器属性。
display 属性
display: grid
指定一个容器采用网格布局。这个跟弹性盒布局 flex 一样
<span>foo</span>
<div id="container">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
<div class="item item-5">5</div>
<div class="item item-6">6</div>
<div class="item item-7">7</div>
<div class="item item-8">8</div>
<div class="item item-9">9</div>
</div>
<span>bar</span>
#container{
display: grid;
grid-template-columns: 50px 50px 50px;
grid-template-rows: 50px 50px 50px;
}
.item {
font-size: 2em;
text-align: center;
border: 1px solid #e5e4e9;
}
.item-1 {
background-color: #ef342a;
}
.item-2 {
background-color: #f68f26;
}
.item-3 {
background-color: #4ba946;
}
.item-4 {
background-color: #0376c2;
}
.item-5 {
background-color: #c077af;
}
.item-6 {
background-color: #f8d29d;
}
.item-7 {
background-color: #b5a87f;
}
.item-8 {
background-color: #d0e4a9;
}
.item-9 {
background-color: #4dc7ec;
}
展示效果

默认情况下,容器元素都是块级元素,但也可以设成行内元素。
#container{
display: inline-grid;
grid-template-columns: 50px 50px 50px;
grid-template-rows: 50px 50px 50px;
}
上面代码将容器元素指定成为了行内块级元素。

注意,设为网格布局以后,容器子元素(项目)的
float
、display: inline-block
、display: table-cell
、vertical-align
和column-*
等设置都将失效。
grid-template-columns 属性, grid-template-rows 属性
容器指定了网格布局之后,接着就要划分行和列。grid-template-columns
属性定义每一列的列宽,grid-tempalge-rows
定义每一列的行高。
.container{
display: grid;
gird-template-rows: 100pxx 100px 100px;
grid-template-columns: 100px 100px 100px;
}
上面的代码指定了一个三行三列的网格,行列的宽高都是100px

当然, 除了使用绝对单位,也可以使用百分比
.container{
dispaly:grid;
grid-template-columns:33.33% 33.33% 33.33%;
grid-template-rows: 33.33% 33.33% 33.33%;
}
(1) repeat()
有时候,重复写同样的值非常麻烦,尤其是网格很多的时候。这是可以使用repeat()函数,简化重复的饿值。上面的代码使用repeat改写如下。
.container{
display:grid;
grid-template-columns: repeat(3, 33.33%);
grid-template-rows: repeat(3, 33.33%);
}
repeat()
接收两个参数,第一个参数是重复的次数,第二个参数是所有要重复的值。
repeat()
重复也可以是某种模式。
grid-template-columns: repeat(2, 100px 20px 80px)
上面的代码定义了6列,第一类和第四列的宽度为100px, 第二类和第五列宽度为20px,第三列和第六列宽度为80px。

这里补充一点,网格一旦划分好,每个网格的大小就固定,不会因为里面的div的大小形式有任何改变。
举个列子,上面的例子中我们的html布局如下:
<div id="container">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
<div class="item item-5">5</div>
<div class="item item-6">6</div>
<div class="item item-7">7</div>
<div class="item item-8">8</div>
<div class="item item-9">9</div>
</div>
.container{
display:grid;
grid-template-row: repeat(3, 100px);
grid-template-columns: repeat(3, 100px);
}
其他的没有任何变化。大家知道现在的展示就是一个九宫格,里面的元素充满每个格子。但是我们现在要给每个 item 里面增加一个如下的样式:
.small{
width:50px;
height:50px;
}
网页就会出现下面的样子,可以看出即使我们吧每个item里面定义了样式,但是一旦在container我们规定了格子的大小,那么格子大小就不会改变。

(2)auto-fill 关键字
有时,单元格的大小是固定的,但是容器的大小确不确定。如果希望每一行(或者每一列) 容纳尽可能多的单元格,这是可以使用 auto-fill 关键字表示自动填充。
.container{
display: gird;
grid-template-columns: repeat(auto-fill, 100px);
gird-template-rows:repeat(3, 100px);
}

(3) fr 关键字
为了方便表示比例关系,网格布局提供了 fr 关键字(fraction的缩写,意为“片段”)。如果两列的宽分别为 1fr 和 2fr,就表示后者是前者的两倍。
.container{
display: grid;
grid-template-columns: 1fr 1fr;
}

上面表示两个相同宽度的列
fr
可以与绝对长度的单位结合使用,这时会非常方便。
.container{
display: grid;
grid-template-columns: 150px 1fr 2fr;
}
上面的代码表示,第一列的宽度为150px, 第二例的宽度是第三列的一半。

(4) minmax()
minmax()
函数产生一个长度的范围,表示长度就在这个范围之中。它接受两个参数,分别为最大值和最小值。这样网格就不会随着容器的任何变化变化,会始终在这个长度范围内波动,类似我们设置 min-width;max-width。
grid-template-columns: 1fr 1fr minmax(100px 1fr)
(5) auto 关键字
auto
关键字表示由浏览器长度自己决定。
grid-template-columns: 100px auto 100px
(6)网格线的名称
grid-template-columns
和 grid-template-rows
属性里面,还可以使用方括号,指定每一根网格线的名字,方便以后的引用。
.container{
display: grid;
grid-template-columns: [c1] 100px [c2] 100px [c3] auto [c4];
grid-template-rows: [r1] 100px [r2] 100px [r3] auto [r4];
}
上面的代指定网格为 3 X 3 的布局,因此有 4 根垂直网格线 和 4 根水平网格线。方括号里面依次是这八根弦的名字。网格布局允许同一根线有多个名字,比如 [fifth-line row-5]
当然了如果没有给网格线起别名,那么浏览器也自动给网格线起一个名字,那就是每个网格线的位置,比如本例中 水平网格线的默认名字是 1 2 3 4,垂直网格线的默认名字同样是 1 2 3 4。 至于为什么给网格线起个名字,那可能是有关网格item的占位有关,具体有什么用会在后面介绍,现在先不多说。
(7)布局实例
grid-template-columns
属性对于网页布局非常有用。两栏布局只需要一行代码。
.wrapper{
display: grid;
gird-template-columns: 70% 30%
}
上面的代码将左栏设置为 70%,右栏设置为30%。
传统的十二栏布局,写起来也很容易。
.wrapper{
display: grid;
grid-template-columns: repeat(12, 1fr);
}
3.3 grid-row-gap 属性, grid-column-gap 属性, grid-gap 属性
布局里面的元素总不能是挨在一起,设计上要有空间感,留白一般是比不可少的。对于里面的留白我们可以使用我们原来的一次CSS属性方法 margin padding 等,但是 栅格布局也有自己的间距属性来控制留白。
gird-row-grap
属性用来设置行与行的间距(行间距),grid-column-grap
属性设置列于列的间距(列间距)。现在这两个属性可以分别简写为 row-grap
和 column-grap
.container{
row-gap: 20px;
column-grap: 20px
}
本文开始时代码,添加以上两个属性后

grid-grap
属性时 grid-column-gap
和 grid-row-gap
的合并简写形式,现简写为 gap 语法如下:
gap: <row-gap> <column-gap>
因此上面一段 CSS 代码等同于以下代码。
.container{
gap: 20px 20px
}
如果 gap
省略了第二个值,浏览器默第二个值等于第一个值。这个跟margin 和 padding 一样的。
实战一下
实现一下小米移动端首页红框里面的布局

可以看到要实现红框里面的布局,其实就是画了四个格子,左边的图片占了两个格子,右边的两个小的图片各占了一个格子。
实现之前先介绍一点 项目上上的属性,用到了上面介绍的网格线名称,我们按照栅格线编号放置元素。
<div class="container">
<div item item-1>1</div>
<div item item-2>2</div>
<div item item-3>3</div>
</div>
.container{
width:300px;
height:300px;
display:grid;
grid-template-rows:repeat(2,1fr);
grid-template-columns: repeat(2, 1fr);
}
.container div:nth-child(1){
grid-row-start: 1; // 元素从行的第几条线开始,如果设置了别名可以使用别名
grid-column-start:1; // 元素从列的第几条开始 如果设置了别名可以使用别名
grid-row-end:3; // 元素在行的第几条线结束
grid-column-end:2; //元素在列的第几条结束
}

使用栅格布局可以轻松的实现了要求,如果使用之前的方法,那就可能就要多画几个div, 设置一些浮动和偏移等。
grid-template-areas 属性
网格布局允许指定 “区域” (area),一个区域由多个单元格组成。grid-template-areas 数据定义区域。
.container{
display:grid;
grid-template-columns:repeat(3, 100px);
grind-template-rows: repeat(3, 100px);
grid-template-areas: 'a b c'
'd e f'
'g h i'
}
上面的代码划分出 9 个单元格,然后将其定义为 a 到 i 的九个区域,分别对应这 9 个单元格。
多个单元格合并成一个区域的写法如下:
grid-template-areas: 'a a a'
'b b b'
'c c c';
下面是一个实例布局
grid-template-areas: "header header header"
"main main sidebar"
"footer footer footer";
上面代码中,顶部是页眉区域,底部是页脚区域,中间则分为 main 和 sidebar。
如果某些某些区域不需要利用,则使用 “点” (.
)表示。
上面代码中,中间一列为点,表示没有用到该单元格,或者该单元格不属于任何区域。
注意,区域的命名会影响到网格线。每个区域的起始网格线,会自动命名为区域名-start,终止网格线自动命名为区域名-end。
比如,区域名为header,则起始位置的水平网格线和垂直网格线叫做header-start,终止位置的水平网格线和垂直网格线叫做header-end。
grid-auto-flow 属性
划分网格以后,容器的子元素会按照顺序,自动放着在每一个网格。默认的防治顺序是“先行后列”,即先填满第一行,在开始放入第二行,即按照下图数字的顺序。

这个顺序由
grid-auto-flow
属性决定,默认值是 row
, 即 “先行后列”。也可以将它设成 column,变成 “先列后行”。这个分 弹性盒模型中 flex-direction 属性类似。
grid-auto-flow: column;
上面代码设置了 column
以后,放置顺序就变成了下图。

grid-auto-flow
属性除了设置成 row
和 column
,还可以设置成 row dense
和 column dense
。这两个值主要用于某些项目指定以后,剩下的项目该怎么自动放置。假设我们让 1号项目和2号项目各占据两个单元格,然后在默认的
grid-auto-flow:row
情况下,会产生下面这样的布局。
上面1号项目后面的位置是空的,这是因为3号项目默认跟着2号项目,所以会排在2号项目后面。
现在修改设置,修改为
row dense
,表示“先行后列”,并且尽可能紧密填满,尽量不出现空格。
grid-auto-flow: row dense
效果如下:

上图会先填满第一行,再填满第二行,所以3号就会紧跟在1号项目的后面。8号和9号项目就会排到第四行。
如果将设置改为
column dense
,表示“先列后行”,并且尽量填满空格。
grid-auto-flow: column dense
上面代码效果

上图会先填满第一列,再填满第二列,所以3号项目在第一列,4号项目在第二列。8号和9号项目被挤到了第四列。
justify-items 属性, align-items 属性, place-items 属性
justify-items
属性设置单元格内容的水平位置(左中右),align-items
属性设置单元格内容的垂直位置(上中下)。
.container {
justify-items: start | end | center | stretch;
align-items: start | end | center | stretch;
}
这两个属性的写法完全相同,都可以取下面的值
start:对齐单元格的起始边缘。
end:对齐单元格的结束边缘。
center:单元格内部居中。
stretch:拉伸,占满单元格的整个宽度(默认值)。
.container {
justify-items: start;
}
表示,单元格内内容左对齐,效果如下:

.container {
align-items: start;
}
表示,单元格的内容头部对齐,效果如下。

place-items
属性是 align-items
和 justify-items
属性的合并简写形式。
// place-items: <align-items> <justify-items>
place-items: start end;
如果省略第二个值,则浏览器认为与第一个值相等。
justify-content 属性, align-content 属性, place-content 属性
justify-content
属性是整个内容区域在容器里面的水平位置(左中右),align-content
属性是整个内容区域的垂直位置(上中下)。
.container {
justify-content: start | end | center | stretch | space-around | space-between | space-evenly;
align-content: start | end | center | stretch | space-around | space-between | space-evenly;
}
这两个属性的写法完全相同,都可以取下面这些值。(下面的图都以justify-content
属性为例,align-content
属性的图完全一样,只是将水平方向改成垂直方向。)
start - 对齐容器的起始边框

end - 对齐容器的结束边框

center - 容器内部居中

stretch - 项目大小没有指定时,拉伸占据整个网格容器

space-around - 每个项目两侧的间隔相等。所以,项目之间的间隔比项目与容器边框的间隔大一倍

space-between - 项目与项目的间隔相等,项目与容器边框之间没有间隔。

space-evenly - 项目与项目的间隔相等,项目与容器边框之间也是同样长度的间隔。

place-content
属性是align-content
属性和justify-content
属性的合并简写形式。
// place-content: <align-content> <justify-content>
place-content: space-around space-evenly;
如果省略第二个值,浏览器就会假定第二个值等于第一个值。
实战 实现一个底部菜单栏
还是以小米官网移动端为例;要实现的状态如下:

要实现移动端官网下面底部菜单栏:我们可以看出底部菜单栏时分成了5个格子,那我们可以做成一个1行5列的布局,假设我们的容器高度时100px;宽度是自适应的不知道,里面每个图标和文字容器宽高是80px。那就可以如下实现。
<div class="container">
<div class="item item-1">1</div>
<div class="item item-2">2<div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
<div class="item item-5">5</div>
</div>
.container{
display:grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: 1fr;
place-items: center;
}
.container div{
width:80px;
height:80px;
}
实现效果如下图所示:

grid-auto-columns 属性, grid-auto-rows 属性
有些时候,一些项目的指定位置,在现有网格的外部。比如网格只有3列,但是某一个项目指定在第五行。这时,浏览器会自动生成多余的网格,一便放置项目。
grid-auto-columns
属性和 grid-auto-rows
属性用来设置浏览器会自动创建的多余网格的列宽和行高。它们的写法与grid-templat-columns
和grid-template-rows
完全相同。如果不指定这两个属性,浏览器会完全根据单元内容的大小决定新增网格的列宽和行高。
下面的例子,划分的网格是 3 X 3,但是8号项目和9号项目指定子第四行,9行项目指定在第五行。
.container{
dispaly: grid;
grid-template-columns: repeat(3, 100px);
gird-template-row: repeat(3, 100px);
grid-auto-rows: 50px; //为新增行指定统一行高度
}
.item-8 {
background-color: #d0e4a9;
grid-row-start: 4;
grid-column-start: 2;
}
.item-9 {
background-color: #4dc7ec;
grid-row-start: 5;
grid-column-start: 3;
}

项目属性
上面描述了定义在容器上的属性,下面这些属性定义在项目上面。
grid-column-start 属性, grid-column-end 属性, grid-row-start 属性, grid-row-end 属性
这些属性上面有提到过,用来指定项目的位置,具体的方法就是指定项目的四个边框,分别定位在哪根网格线。
grid-column-start属性:左边框所在的垂直网格线
grid-column-end属性:右边框所在的垂直网格线
grid-row-start属性:上边框所在的水平网格线
grid-row-end属性:下边框所在的水平网格线
.item-1 {
grid-column-start: 2;
grid-column-end: 4;
}
上面的代码指定, 1号项目的左边框是第二根垂直网格线,右边框是第四根垂直网格线。

上图中,只指定了1号项目的左边框和右边框,没有指定上下边框,所以会采用默认位置,即上边框是第一根水平网格线,下边框是第二根水平网格线。
除了1号项目以外,其他项目都没有指定位置,由浏览器自动布局,这时它们的位置由容器的grid-auto-flow
属性决定,这个属性的默认值是row
,因此会"先行后列"进行排列。读者可以把这个属性的值分别改成column
、row dense
和column dense
,看看其他项目的位置发生了怎样的变化。
下面的例子是指定四个边框位置的效果。
.item-1 {
grid-column-start: 1;
grid-column-end: 3;
grid-row-start: 2;
grid-row-end: 4;
}

这四个属性的值,除了指定为第几个网格线,还可以指定为网格线的名字。
.item-1 {
grid-column-start: header-start;
grid-column-end: header-end;
}
上面代码中,左边框和右边框的位置,都指定为网格线的名字。
这四个属性的值还可以使用span
关键字,表示"跨越",即左右边框(上下边框)之间跨越多少个网格。也是根据偏移量对元素进行定位。使用过 element-UI 的朋友会比较熟悉。<el-col :span='4'></el-col>
下面代码表示,1号项目的左边框距离有边框跨越2个网格。
.item-1 {
grid-column-start: span 2;
}

这与下面的代码效果完全一样
.item-1 {
grid-column-end: span 2;
}
使用这四个属性,如果产生了项目的重叠,则使用z-index
属性指定项目的重叠顺序。
grid-column 属性, grid-row 属性
grid-column
属性是grid-column-start
和grid-column-end
的合并简写形式,grid-row
属性是grid-row-start
属性和grid-row-end
的合并简写形式。
.item {
grid-column: / ;
grid-row: / ;
}
下面是一个例子
.item-1 {
grid-column: 1 / 3;
grid-row: 1 / 2;
}
/* 等同于 */
.item-1 {
grid-column-start: 1;
grid-column-end: 3;
grid-row-start: 1;
grid-row-end: 2;
}
上面代码中,项目item-1
占据第一行,从第一根列线到第三根列线。
这两个属性之中,也可以使用span
关键字,表示跨越多少个网格。
.item-1 {
background: #b03532;
grid-column: 1 / 3;
grid-row: 1 / 3;
}
/* 等同于 */
.item-1 {
background: #b03532;
grid-column: 1 / span 2;
grid-row: 1 / span 2;
}
上面代码中,项目item-1
占据的区域,包括第一行 + 第二行、第一列 + 第二列。

斜杠以及后面的部分可以省略,默认跨越一个网格。
.item-1 {
grid-column: 1;
grid-row: 1;
}
上面代码中,项目item-1
占据左上角第一个网格。
实战 自主实现一个类似 bootstrap 栅格系统
bootstrap栅格系统是把一行分成12个格子,然后使用 col-1,2,3,4,5.... 分别表示跨越几列。对于实现一个非响应式的这样的栅格系统有了 grid 我们实现起来就非常的方便了。
.row{
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 10px; // 网格之间给10个间距
}
.col-1{
grid-column-end:span 1; //在原来的技术长列向后偏移一个单位
}
.col-2{
grid-column-end:span 2; //在原来的技术长列向后偏移一个单位
}
.col-3{
grid-column-end:span 3; //在原来的技术长列向后偏移一个单位
}
/*一次类推到12 就是闲了我们需要栅格布局*/
/*当然了里面的属性也可以简写*/
.col-1{
grid-column: span 1;
}
grid-area 属性
grid-area
属性指定项目放在哪一个区域。
#container{
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
grid-template-areas: 'a b c'
'd e f'
'g h i';
}
.item-1 {
grid-area: e;
}
以上代码, 1号项目位于 e
区域,效果如下图所示

grid-area
属性还可用作grid-row-start
、grid-column-start
、grid-row-end
、grid-column-end
的合并简写形式,直接指定项目的位置。
.item {
grid-area: <row-start> / <column-start> / <row-end> / <column-end>;
}
.item-1 {
grid-area: 1 / 1 / 3 / 3;
}
实战 实现小米移动端首页布局
要实现的效果如下图所示

实现该布局的网格划分可以下面的形式:

可以看出我们将真个网页划分成一个三行两列的结构,下面就开始实现该布局。
.container{
width:100vw;
height:100vh;
display:grid;
gap:10px;
gird-template-rows:60px 1fr 60px;
gird-template-columns:60px 1fr;
gird-template-areas: 'header header'
'nav main'
'footer footer'
}
.item {
font-size: 4em;
text-align: center;
}
.item-1 {
grid-area:header;
background-color: #ef342a;
}
.item-2 {
grid-area:nav;
background-color: #f68f26;
}
.item-3 {
background-color: #4ba946;
}
.item-4 {
grid-area:footer;
background-color: #0376c2;
}
<div class="container">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
</div>

justify-self 属性, align-self 属性, place-self 属性
justify-self
属性设置单元格内容的水平位置(左中右),跟justify-items
属性的用法完全一致,但只作用于单个项目。
align-self
属性设置单元格内容的垂直位置(上中下),跟align-items
属性的用法完全一致,也是只作用于单个项目。
.item {
justify-self: start | end | center | stretch;
align-self: start | end | center | stretch;
}
这两个属性都可以取下面四个值。
start:对齐单元格的起始边缘。
end:对齐单元格的结束边缘。
center:单元格内部居中。
stretch:拉伸,占满单元格的整个宽度(默认值)。
下面是justify-self: start
的例子。
.item-1 {
justify-self: start;
}

place-self
属性是align-self
属性和justify-self
属性的合并简写形式。
place-self: <align-self> <justify-self>;
place-self: center center;
如果省略第二个值,place-self
属性会认为这两个值相等。
写在最后
本来觉得写CSS这部分的内容应该是比较容易,但是发现写下来还真的很多,疫情期间家里储备弹尽粮绝,饿着写完了这一部分的内容。就是搬运也很耗时间,吐槽一下简书对于Typora兼容行还是不够好,直接copy过来很多的错误和乱码,还要重新写一遍。。希望自己能够再接再厉,继续写下去!
网友评论