給Markdown渲染網頁增加一個目錄組件(Vite+Vditor+Handlebars)(下)
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:
<!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是:
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布局:
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布局:
#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布局還有一個好處,那就是可以實現響應式布局。所謂響應式布局,指的是使網頁能夠在不同的分辨率下都有比較好的瀏覽體驗。結合本文的例子來說:
#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 包含了三個子屬性:
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)(上)》中的問題:博文目錄是如何始終保證粘在頁面的右上角的?因為使用了粘性定位:
#article-toc-placeholder {
position: sticky;
top: 0;
}
這里的意思就是當博文目錄控件離開視圖頁面的時候,就將博文目錄控件的位置粘在頁面的頂部(top屬性值為0的位置)。看起來這個實現非常簡單,但是如果在一些復雜的情況下使用會有一些問題,因為粘性定位本質上是脫離了HTML文檔流的。比如說,將博文目錄粘在右側控件的頂部還好,如果是粘在其他特定的位置,就需要精確地控制位置屬性,否則就很容易與其他頁面元素沖突。
不止粘性定位,浮動(float)、絕對定位(position: absolute)、固定定位(position: fixed)都會脫離HTML文檔流,不推薦用來作為主要的布局方式,這里就不細說了。
4 結語
最終的博文目錄粘在頁面右上角的效果如下所示:


浙公網安備 33010602011771號