CSS 布局的本質是什么
本文轉載自微信公眾號「神光的編程秘籍」,作者神說要有光zxg。轉載本文請聯系神光的編程秘籍公眾號。
UI 發展史
自從圖形界面操作系統問世以來,之上的應用軟件基本都會繪制界面,這也是用戶使用軟件的接口,叫做 UI (user interface)。涉及到用戶體驗、設計、具體界面的開發,是軟件中和用戶最近的一部分,也是多個職能的崗位交集最多的部分。
根據操作系統不同,會有不同的界面的開發方式。安卓、ios、windows 等都有各自的創建 ui 的庫,但是更底層的繪圖庫卻是有標準的:跨平臺的繪圖 api 接口標準 OpenGL 以及 windows 下的 DirectX。
因為各個操作系統繪制 ui 的方式不同,所以跨平臺的繪制方案逐漸流行開來,也就是瀏覽器。基于瀏覽器服務器的軟件架構叫做 B/S 架構,而基于客戶端的叫做 C/S 架構。
在一段時間內,B/S 架構的應用越來越多,C/S 架構的應用也更多的混合 B/S 的方案來實現。
在移動互聯網時代來臨之后,大家發現網頁的體驗比不上原生,雖然后面發展出了 PWA (漸進式WebApp)等技術,但離原生的體驗還是有差距的,所以原生開發應用的方案又占了上風。
但是安卓、ios 繪制界面、書寫邏輯的方式都不同,雙端要分別實現,開發、測試的人力都是雙份的,這樣的成本是比較高的。為了節省成本,大家又摸索出了跨端引擎的方案,也就是說還是通過網頁來寫渲染和交互的邏輯,但是渲染用的 api 是由安卓、ios 分別實現,這樣就實現了跨端的渲染,邏輯部分也是由 JS 來寫,一些需要的設備能力 api 分別由安卓、ios 實現然后注入到 JS 引擎里。
和安卓、ios 的跨端方案逐漸流行一樣,桌面端也出現了 electron 的方案,通過網頁來渲染界面和寫邏輯,需要用的 api 注入到 JS 引擎中,而且 electron 是直接把 Node.js 的 api 注入到了 JS 引擎中,在網頁里實現一些原生功能的時候可以直接使用 Node.js 的 api,此外還有一些 api 是 elctron 額外注入的,比如剪貼板、電源監視器等。
發展到現在,UI 的繪制方案逐步向網頁靠攏,基于 html、css、js 的 web 技術成為了創建 UI 界面的主流方案。
網頁的物理層和邏輯層
大家用過 canvas 的 api 應該知道,如果直接繪制的話需要指定什么內容繪制到什么地方,每一部分都要計算,而這是比較繁瑣的,所以瀏覽器提供了一些布局用的樣式,并且提供了 css 來描述,而內容部分則是通過 html 描述。
開發者只需要使用 html 描述內容的結構,然后用 css 來描述布局和如何渲染,就可以完成界面的繪制。網頁會把 html 解析成 dom 樹,把 css 解析成 cssom 樹,之后把兩者合并成 render 樹,自動計算出什么內容繪制到什么位置,實現最終的渲染。
dom 是有 id、class、tagName 等標識的,邏輯的部分就通過這些標識給具體 dom 綁定一些事件處理函數,然后在函數里面操作 dom 來實現的界面的交互。
dom api 是最終瀏覽器提供給開發者的構建 web 應用的接口,算是 web 應用的物理層。
當然,現在開發 web 應用并不會直接基于 dom api,而是會選擇某一個前端框架,比如 vue、react、angular 等。
這些框架實現了組件的功能,也就是對頁面做的邏輯的拆分,把相同功能的 html、css、js 聚合在一起,使之可以復用。并且提供了 mvvm 的功能,自動做數據到具體 dom 的映射,而不再需要開發者手動操作 dom。
前端框架做的事情相當于是 web 應用的邏輯層,最終的渲染和交互還是通過 dom api,但是用戶不需要直接操作,而是在邏輯層描述組件和數據,由前端框架完成數據到 dom 的自動映射。
現在的跨端方案基本都是對物理層的 dom api 做了替換,然后上層對接一個邏輯層的前端框架來支持跨端的應用開發。
css 的兩部分
css 是瀏覽器提供給開發者的描述界面的方式,而描述界面分為兩部分:
- 內容繪制在什么地方
- 內容怎么繪制
內容繪制在什么地方就是布局的部分,主要是 display 和 position 的樣式。而內容怎么繪制則跟具體內容相關,font、text、image 等內容都有各自的一些樣式。
本文我們主要來探究 css 做布局的部分。
盒模型
首先,所有的內容都會有一些空白和與其他元素的間距,所以 css 抽象出了盒模型的概念,也就是任何一個塊都是由 content、padding(空白)、border、margin(間距)這幾部分構成。
display
但是盒與盒之間也是有區別的,有的盒可以在同一行顯示,有的則是獨占一行,而且對內容的位置的計算方式也不一樣。于是提供了 display 樣式來設置盒類型,比如 block、inline、inline-block、flex、table-cell、grid 等,分別設置成不同的盒類型,就會使用不同的計算規則。
- block 的元素會獨占一行、可以設置內容的寬高,具體計算規則叫做 BFC。
- inline 的元素寬高由內容撐開不可設置,不會獨占一行,具體計算規則叫做 IFC。
- flex 的子元素可以自動計算空白部分,由 flex 樣式指定分配比例,具體計算規則叫做 FFC。
- grid 的子元素則是可以拆分成多個行列來計算位置,具體計算規則叫 GFC。
這些都是不同盒類型的布局計算規則。
position
根據不同盒類型的布局計算規則往往不夠用,很多場景下需要一些用戶自定義的布局規則,所以 css 提供了 position 樣式,包括 static、relative、absolute、fixed、sticky。
static
默認盒的定位方式就是 static,也就是流式的,上個盒子顯示到什么地方了,下個盒子就在下面繼續計算位置,顯示在什么位置是由內容多少來決定的。
最開始的時候網頁主要是用來顯示一些文本,所以流式的位置計算規則就很方便。
relative
流式的規則是根據上個盒子的位置自動計算出下個盒子的位置,但有的時候想做一些偏移,這種就可以通過 relative 來指定,設置 position 為 relative,然后通過 top、bottom、left、right 來指定如何偏移。
相對布局給流式布局增加一些靈活性,可以在流式計算規則的基礎上做一些偏移。
absolute
流式的計算規則具體什么內容顯示在什么位置是不固定的,只適合文字、圖片等內容的布局。但是比如一些面板需要固定下來,就在某個位置不要動,就可以通過 position 設置為 absolute,就可以脫離文檔流了。這時候就可以根據上個非流式的 position 來計算現在的 position。
fixed
absolute 是根據上一個脫離了文檔流的 position 來計算位置的,最外層的 absolute 的元素是根據窗口定位。如果想直接根據窗口來定位可以指定 position 為 fixed。這個時候的 top、bottom、left、right 就是相對于窗口的。
sticky
sticky 的效果在滾動的時候如果超過了一定的高度就 fixed 在一個位置,否則的話就 static。相當于基于 static 和 fixed 做的一層封裝,實現導航條吸頂效果的時候可以直接用。
或許就是因為太常用,才封裝出了這樣一個 position 的屬性值吧,之前都是通過 js 監聽滾動條位置來分別設置 static 和 fixed 的。
小結
所謂的布局就是確定元素的位置,設置了盒的類型(display)之后對于內容如何渲染會有不同的規則,比如 BFC、IFC、FFC、GFC 等。
盒與盒之間默認是流式的,也就是 position 為 static,但有的時候想在流中做下偏移,用 relative。當不想跟隨文檔流了,可以設置 absolute 來相對于上個非 static 位置來計算一個固定的位置,如果想直接相對于窗口,就用 fixed。
當需要做吸頂效果的時候,要根據滾動位置切換 static 和 fixed,這時候 css 還有一個 sticky 的定位方式可以直接用。
也就是說,盒內部的布局計算規則根據 display 來確定,還可以用 position 做一些調整。
vscode 是如何布局的
講了 css 的布局方式(也就是 display 配合 position)之后,我們來看一個具體的案例:vscode 是如何布局的。
vscode 是我們經常用的 ide,它基于 electron,也就是通過網頁來繪制界面,那么它是怎么做布局的呢?
vscode 分為了標題欄、狀態欄、內容區,是上中下結構,而內容區又分為了活動欄、側邊欄、編輯區,是左中右結構。窗口可以調整大小,而這個上中下嵌套左中右的結構是不變的。
這種布局如何實現呢?
css 的布局就是 display 配合 position 來確定每一塊內容的位置。我們的需求是窗口放縮但每一塊的相對位置不變,這種用 absolute 的布局就可以實現。
首先,最外層是上中下的結構,可以把每一塊設置為 absolute,然后分別設置 top 值,然后中間部分由分為了左中右,可以再分別設置左中右部分的 left 值,這樣就完成了每一塊的布局。
這是整體的布局,每一塊內部則根據不同的布局需求分別使用流式、彈性等不同的盒,配合絕對、相對等定位方式來布局。
但是,絕對定位是要指定具體的 top、bottom、left、right 值,是靜態的,而窗口大小改變的時候需要動態的設置具體的值。這時候就需要監聽窗口的 resize 事件來重新布局,分別計算不同塊的位置。
而且 vscode 每一塊的大小是也是可以拖動改變大小的,也要在拖動的時候重新計算 left、top 的值。
總結
現代軟件基本都是有用戶界面的,而不同操作系統下構建 UI 的方式不同,所以跨平臺渲染的瀏覽器的方案逐漸流行開來。移動互聯網時代之后,為了綜合原生的體驗和網頁的跨平臺,出現了跨端引擎的方案,也就是基于安卓、ios 分別實現 dom api 并注入一些設備能力的 api 給 JS 引擎,業務代碼通過 dom api 來描述 UI。
dom api 是瀏覽器提供給開發者的描述 UI 的方式,是物理層。現在的前端框架可以完成組件的封裝和數據到 dom 的映射,不再需要直接操作 dom,算是邏輯層。
因為跨端引擎實現了 dom api,所以上層可以對接前端框架。
UI 是通過 css 來描述的,而 css 可以分為兩部分:布局和具體元素的渲染。
具體 font、text、image 等分別有不同的樣式來描述如何渲染,而布局是確定每個元素的位置,由 display 配合 position 來確定。
網頁的每一個內容都是一個盒,由 content、padding、border、margin 構成,而 display 是設置盒的類型,不同的盒有不同的布局規則,比如 BFC、IFC、FFC、GFC 等。
當有一些需要定制的布局規則,可以使用 position。默認的 position 是 static,也就是流式的,根據上個盒來確定下個盒的位置,可以使用 relative 做一些偏移,如果想相對于某個位置固定,可以使用 absolute,當直接相對窗口的時候使用 fixed。此外,在做吸頂效果的時候,可以使用 sticky,它是基于 static 和 fixed 的封裝。
知道了 display 和 position 都怎么做布局,也就是計算盒的位置以后,我們看了下 vscode 是怎么布局的。
vscode 是上中下嵌套左中右的結構,窗口改變或者拖動都可以調整每塊大小,所以使用嵌套的 absolute 的方式來做整體的布局。每一塊的內部則綜合使用流式、彈性等方式配合 position 分別做更細節的布局。
網頁的 css 布局方案已經應用在越來越多的領域,比如跨端引擎通過安卓、ios 實現 css,kraken 基于 flutter 實現 css,所以 css 的布局方式是我們必須掌握的技能。希望這篇文章能幫大家梳理清楚 css 布局的思路,對各種布局都能夠分析清楚特性,然后用合適的方案來實現。