1 引言
在上一篇文章《给Markdown渲染网页增加一个目录组件(Vite+Vditor+Handlebars)(上)》中笔者介绍了如何实现在Markdown渲染网页中加一个目录组件。不过,上一篇文章中只是介绍了一下功能也就是JavaScript部分的具体实现。其实要实现这个功能,另外一个关键就是样式也就是CSS部分的设计。
阅读本文可能需要的前置文章:
- 《通过JS模板引擎实现动态模块组件(Vite+JS+Handlebars)》
- 《使用Vditor将Markdown文档渲染成网页(Vite+JS+Vditor)》
- 《给Markdown渲染网页增加一个目录组件(Vite+Vditor+Handlebars)(上)》
2 基础
在前端三剑客(HTML+CSS+JavaScript)中笔者认为CSS是最麻烦的,具体就麻烦在CSS并不是一个遵循程序员思维的语言,反而它遵循的是设计师的思维:要求对排版规则的掌控,要求能熟能生巧的积累,最好还要有一点美术审美。大部分的计算机语言都相通,但是CSS却特立独行。
另外,笔者觉得市面上关于CSS的中文教程也大多不太行,很多都是CSS属性的堆砌。笔者记得学习过一本CSS教程,光是开头介绍字体的设置就讲了一个很大的章节,这真的很难让初学者入门。有的人说CSS的属性要靠记忆,这是文科的思维,有一点道理在里面;但是笔者认为CSS作为一门计算机语言,还是有一些理性思维在里面的。其中,最理性最程序员思维的部分就是网页布局:设计出来的页面中的元素,至少要听你指挥,出现在你想要放置的位置。
2.1 盒子模型
盒子模型是网页布局中最基础的概念,定义了如何处理单个HTML元素。具体来说,就是每一个HTML元素都被看作是一个矩形的盒子,这个盒子由内容(content)、内边距(padding)、边框(border)和外边距(margin)组成。这个概念应该来说属于老生常谈了,基本上每个介绍CSS的教程都会首先介绍它。其实要掌握这个概念不用费那么多精力,对着浏览器后台开发工具调试一下padding、border和margin属性,看看每个参数的效果就可以了。如下图所示:

2.2 HTML文档流
HTML文档流(Document Flow)也是网页布局最基础概念之一,指的是HTML页面中的元素,默认按照从上到下、从左到右的顺序对页面元素进行排列和渲染的方式。这个理论听起来非常自然,甚至有点像废话文学;但确实就是网页布局的关键所在:
- HTML页面中的元素如果没有进行特定的排版设置,那么从上到下、从左到右的顺序就是HTML元素的默认位置,这让我们确定了通过CSS调整样式的起始值。
- HTML页面中元素大部分处于文档流中,即使通过CSS调整了位置,也只是改变了局部的布局方式,大体上仍然遵循这个规则。
- 大多数情况下HTML页面中的元素最好不要脱离文档流,因为脱离文档流往往需要比较精细的控制,除非少部分需求真的需要这么做。
在文档流中,HTML元素可以分为三种类型:
类型 | 特点 | 示例标签 |
---|---|---|
块级元素 | 独占一行,自动换行,可设置宽高 | div , p , h1 到 h6 |
行内元素 | 不独占一行,宽度由内容决定,不能设置宽高 | span , a , strong |
行内块元素 | 行内显示,但可以设置宽高 | display: inline-block |
2.3 现代布局方式
与网页布局最直接相关的属性就是display
,最基础的几种属性值如下:
display: none;
元素不会被显示,也不占据任何空间,常用于隐藏元素。display: block;
元素作为块级元素显示(独占一行),可以设置宽度、高度、内外边距。前面介绍的块级元素display
的默认属性值就是block
。display: inline;
元素作为内联元素显示(和其他内联元素在同一行),不能设置宽度和高度。前面介绍的行内元素display
的默认属性值就是inline
。display: inline-block;
结合了inline
和block
的特性,可以在同一行显示,并且可以设置宽度、高度、内外边距。常用于横向排列的导航栏或按钮组。
对于现代网页设计来说,更为重要的是display: flex;
和display: grid;
这两种属性值。其中display: flex;
更为重要一点,也就是所谓的弹性盒子布局(Flexbox)。
3 实现
3.1 弹性盒子布局
那么接下来就结合本文的具体实例来讲解Flexbox布局。回到本例的index.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app">
<div id="post-article-placeholder"></div>
<div id="article-toc-placeholder"></div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
对应的样式文件style.css是:
css
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f0f0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#app {
/* 将容器设置为弹性容器。 */
display: flex;
flex-direction: row;
justify-content: center;
align-items: start;
gap: 1rem;
margin-top: 1rem;
width: 100%;
}
#post-article-placeholder {
min-width: 600px;
max-width: 800px;
flex: 1 1 auto; /* 允许伸缩 */
}
#article-toc-placeholder {
width: 260px;
position: sticky;
top: 0;
flex: 0 0 auto; /* 固定宽度不伸缩 */
}
在这里,我们想让app元素(包含博文内容控件和博文目录控件)居中显示,就设置根元素body
为Flexbox布局:
css
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
这里的样式设置的意思是:
flex-direction
定义主轴方向,设置成column
表示body
中的元素像列一样布局,也就是垂直布局。justify-content
定义项目在主轴上的对齐方式,设置成center
表示垂直方向上居中对齐。align-items
定义项目在交叉轴上的对齐方式,设置成center
表示在水平方向上居中显示。
app元素设置成居中之后,解下来我们想让app元素的子元素博文内容控件和博文目录控件左右布局,并且顶端对齐。要实现这个功能:当然还是要设置成Flexbox布局:
css
#app {
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}
而这里的样式设置的意思是:
flex-direction: row;
表示app
中的元素像行一样布局,也就是左右水平布局。justify-content: center;
表示app
中的元素在水平方向上向中心靠齐。align-items: flex-start
表示app
中的元素在垂直方向上,沿着起始位置靠齐,也就是顶端对齐。
可以调整一下这些属性的值来加深对布局的理解。比如align-items
的值设置成center
,那么博文目录控件就会与博文内容控件在垂直居中对齐;设置成flex-end
博文目录控件则会与博文内容控件底部对齐。justify-content
的设置在某些情况下非常有用:比如需要在水平方向上(这里的主轴方向)靠左对齐,就设置成flex-start
;需要靠右对齐,就设置成flex-end
;需要两端对齐,就设置成space-between
。
Flexbox布局的好处就在这里,它可以通过设置垂直布局和水平布局,形成了一种对网页布局的通解:只要你拆分的盒子数量和层级够多,那么网页元素可以始终控制在HTML文档流中。换句话说,一个前端程序员的基本素质就是将原型转换成多层级的包含垂直/水平布局的盒子。比如说稀土掘金网站的首页,根据其布局我们可以进行如下拆分:

3.2 响应式布局
使用Flexbox布局还有一个好处,那就是可以实现响应式布局。所谓响应式布局,指的是使网页能够在不同的分辨率下都有比较好的浏览体验。结合本文的例子来说:
css
#app {
display: flex;
}
#post-article-placeholder {
flex: 1 1 auto; /* 允许伸缩 */
min-width: 600px;
max-width: 800px;
}
#article-toc-placeholder {
width: 260px;
flex: 0 0 auto; /* 固定宽度不伸缩 */
}
这里的flex
包含了三个子属性:
css
flex: <flex-grow> <flex-shrink> <flex-basis>;
具体的含义是:
子属性 | 描述 |
---|---|
flex-grow |
定义项目的放大比例,默认为 0(不放大) |
flex-shrink |
定义项目的缩小比例,默认为 1(可缩小) |
flex-basis |
定义项目在分配多余空间之前的初始大小,可以是长度值(如 200px )、百分比(如 auto )等 |
对于博文内容控件,flex: 1 1 auto;
的意思是:
flex-grow: 1
:该元素会尽可能地占据剩余空间。flex-shrink: 1
:该元素在空间不足时可以被压缩。flex-basis: auto
:该元素的初始宽度由其内容或显式设置的width
决定。
对于博文目录控件,flex: 0 0 auto;
的意思是:
flex-grow: 0
:该元素不会扩展。flex-shrink: 0
:该元素不会压缩。flex-basis: auto
:项目的初始大小由其内容或显式设置的width
决定。
也就是说,博文目录的宽度保持原有大小不参与伸缩;而博文内容控件则会根据容器空间进行伸缩。这也是这种布局方式被称为弹性盒子布局的原因,在这种布局方式下,页面中的元素可以灵活响应页面分辨率宽高的变化。不过,在现代网页中文档内容区域的宽度范围通常在600px
到800px
之间,这里就设置博文内容控件的最小宽度为600px
,最大宽度为800px
。
当然响应式布局的内涵不止这一点,响应式布局最终希望在不同设备(如桌面电脑、平板电脑和手机)上都有最佳的浏览体验,这需要自动根据屏幕尺寸和方向自动调整布局结构、元素大小及位置;要实现这一点还是离不开Flexbox布局。
3.3 粘性定位
在这里,笔者就回答一下上一篇文章《给Markdown渲染网页增加一个目录组件(Vite+Vditor+Handlebars)(上)》中的问题:博文目录是如何始终保证粘在页面的右上角的?因为使用了粘性定位:
css
#article-toc-placeholder {
position: sticky;
top: 0;
}
这里的意思就是当博文目录控件离开视图页面的时候,就将博文目录控件的位置粘在页面的顶部(top
属性值为0的位置)。看起来这个实现非常简单,但是如果在一些复杂的情况下使用会有一些问题,因为粘性定位本质上是脱离了HTML文档流的。比如说,将博文目录粘在右侧控件的顶部还好,如果是粘在其他特定的位置,就需要精确地控制位置属性,否则就很容易与其他页面元素冲突。
不止粘性定位,浮动(float
)、绝对定位(position: absolute
)、固定定位(position: fixed
)都会脱离HTML文档流,不推荐用来作为主要的布局方式,这里就不细说了。
4 结语
最终的博文目录粘在页面右上角的效果如下所示:
