【Taro】363- 玩轉 Taro 跨端之 flex 布局篇
- 2019 年 10 月 11 日
- 筆記
Taro 是一套遵循 React 語法規範的跨平台開發解決方案,但是目前當我們使用 Taro 的時候,在不同平台上的開發體驗還有不一致的地方,所以我們也都期待有一套跨平台統一的解決方案,能夠以最小差異的方式向開發者提供更好的開發體驗。
在跨平台開發的過程中,不同平台之間的差異尤其體現在樣式的統一上,由於不同平台對樣式的支持程度並不一致,Taro 很難能夠通過編譯的手段來對跨平台樣式進行統一,所以,我們需要一個支持跨平台的樣式解決方案來對其進行統一。
跨平台樣式
考慮頁面布局和樣式 H5 是最為靈活的,小程序次之,React Native 和快應用最弱,統一跨平台樣式應當優先對齊短板,也就是要以 React Native 和快應用的約束來管理樣式,同時兼顧小程序的限制,而 Flexbox 就是一個很好的樣式解決方案。
在構建頁面的時候,我們可以通過 Flexbox 高效地完成頁面代碼,雖然並不是所有屬性都可以全平台適應的,但是它在全平台都能夠得到足夠的支持,而且所有平台可以很容易通過它來繪製通用性很高的頁面,這也就是為什麼我們選擇使用 Flexbox 方案來完成這個跨平台演示項目。

Flexbox 布局 (Flexible Box Layout)
Flexbox 是彈性布局模塊(CSS Flexible Box Layout Module)常用的簡稱,是一種用於在單個維度中顯示項目行或列的布局模型。在規範中, Flexbox 被描述為用戶界面設計的布局模型。Flexbox 的關鍵特性是 flex 布局中的項目可以增長和縮小。可以將空間分配給項目本身,或者在項目之間或周圍分配空間。
在 Flexbox 中,採用 flex 布局 的元素,稱為 flex 容器(flex container), flex 容器所有的子元素自動成為容器成員,稱為 flex 元素(flex item)。Flex 容器 默認存在兩根軸:水平的主軸(main-axis)和垂直的交叉軸(cross-axis)。flex 元素 默認沿主軸排列。主軸的開始位置(與邊框的交叉點)叫做 main-start ,結束位置叫做 main end ;交叉軸的開始位置叫做 cross-start ,結束位置叫做 cross-end ;單個項目佔據的主軸空間叫做 main-size ,佔據的交叉軸空間叫做 cross-size 。

Flexbox 可以對齊主軸或橫軸上的項目,從而提供對一組項目的大小和對齊的高級控制,大多數場景下,使用 flex-direction、align-items 和 justify-content 三個樣式屬性就已經能滿足大多數布局需求,換而言之如果熟悉 Flexbox 就可以應對大多數場景下的布局需求。
注意,設為 Flex 布局以後,子元素的 float 、 clear 和 vertical-align 屬性將失效。請閱讀下方文本熟悉工具使用方法。
Flex Container 屬性
在規範中, Flex Container 上,一共有七個屬性可以設置,但是 flex-flow 在 React Native 上是不支持的。
flex-direction
flex-direction 屬性指定了flex 元素是如何在 flex 容器中布局的,定義了主軸的方向(正方向或反方向)。
支持的值如下:
值 |
意義 |
---|---|
row |
flex 容器的主軸被定義為與文本方向相同。主軸起點和主軸終點與內容方向相同。 |
row-reverse |
表現和 row 相同,但是置換了主軸起點和主軸終點。 |
column |
flex 容器的主軸和塊軸相同。主軸起點與主軸終點和書寫模式的前後點相同。 |
column-reverse |
表現和 column 相同,但是置換了主軸起點和主軸終點。 |
需要注意的是,規範下 flex-direction 的默認值是 row ,而在 React Native 中則為 column,這也就是為什麼我們會添加了這個的樣式
.flex { display: flex; flex-direction: row; }
flex-wrap
flex-wrap 指定 flex 元素單行顯示還是多行顯示。如果允許換行,這個屬性允許控制行的堆疊方向。默認值為 nowrap。
支持的值如下:
值 |
意義 |
---|---|
nowrap |
不換行。flex 元素被擺放到到一行,這可能導致溢出 flex 容器。交叉軸的起點會根據 flex-direction 的值相當於 start 或 before。 |
wrap |
flex 元素被打斷到多個行中。交叉軸的起點會根據 flex-direction 的值選擇等於start 或before。交叉軸的終點為確定的交叉軸的起點的另一端。 |
wrap-reverse |
和 wrap 的行為一樣,但是交叉軸的起點和交叉軸的終點互換。 |
使用 flex-wrap 屬性的時候,我們需要注意 wrap-reverse 值在 React Native 上是不支持的。
flex-flow
flex-flow 屬性是 flex-direction 和 flex-wrap 的簡寫。默認值為 row nowrap。
語法格式
<'flex-direction'> || <'flex-wrap'>
flex-flow 屬性不被 React Native 和快應用支持
align-items
align-items 屬性將所有直接子節點上的 align-self 值設置為一個組。align-self 屬性設置項目在其包含塊中在交叉軸方向上的對齊方式。默認值為 stretch。
值 |
意義 |
---|---|
stretch |
flex 元素在交叉軸方向拉伸到與容器相同的高度或寬度(flex 元素不能固定尺寸) |
flex-start |
交叉軸的起點對齊 |
flex-end |
交叉軸的終點對齊 |
center |
交叉軸的中點對齊 |
baseline |
元素第一行文字的基線對齊 |
語法格式
normal | stretch | <baseline-position> | [ <overflow-position>? <self-position> ] where <baseline-position> = [ first | last ]? baseline <overflow-position> = unsafe | safe <self-position> = center | start | end | self-start | self-end | flex-start | flex-end
baseline 值不被 React Native 和快應用支持 space-evenly、start、end、self-start、self-end、left、right、first baseline、last baseline、safe、unsafe 在 flex 布局中通用性低
align-content
align-content 屬性設置了如何沿着 flex 容器的交叉軸和在 flex 元素之間和周圍分配空間。默認值為 stretch。
該屬性對單行彈性盒子模型無效。(即:帶有 flex-wrap: nowrap 的 flex 容器)。
值 |
意義 |
---|---|
stretch |
拉伸所有 flex 元素來填滿剩餘空間。剩餘空間平均的分配給每一個 flex 元素 |
flex-start |
所有 flex 元素從垂直軸起點開始填充。第一個 flex 元素的垂直軸起點邊和 flex 容器的垂直軸起點邊對齊。接下來的每一個 flex 元素緊跟前一個 flex 元素。 |
flex-end |
所有 flex 元素從垂直軸末尾開始填充。最後一個 flex 元素的垂直軸終點和容器的垂直軸終點對齊。同時所有後續 flex 元素與前一個對齊。 |
center |
所有 flex 元素朝向容器的中心填充。每 flex 元素互相緊挨,相對於容器居中對齊。容器的垂直軸起點邊和第一個 flex 元素的距離相等於容器的垂直軸終點邊和最後一個 flex 元素的距離。 |
space-between |
所有 flex 元素在容器中平均分佈。相鄰兩 flex 元素間距相等。容器的垂直軸起點邊和終點邊分別與第一個 flex 元素和最後一個 flex 元素的邊對齊。 |
space-around |
所有 flex 元素在 flex 容器中平均分佈,相鄰兩 flex 元素間距相等。容器的垂直軸起點邊和終點邊分別與第一個 flex 元素和最後一個 flex 元素的距離是相鄰兩 flex 元素間距的一半。 |
space-evenly |
flex 元素都沿着主軸均勻分佈在指定的 flex 元素中。相鄰 flex 元素之間的間距,主軸起始位置到第一個 flex 元素的間距,,主軸結束位置到最後一個 flex 元素的間距,都完全一樣。 |
語法格式
normal | <baseline-position> | <content-distribution> | <overflow-position>? <content-position> where <baseline-position> = [ first | last ]? baseline <content-distribution> = space-between | space-around | space-evenly | stretch <overflow-position> = unsafe | safe <content-position> = center | start | end | flex-start | flex-end
React Native 中需要版本號在 0.58 以上 且 flex-wrap 屬性值需要為 wrap 同時只對橫軸生效(即 flex-direction 屬性為 column 或 column-reverse) baseline 值不被 React Native 支持 space-evenly、start、end、left、right、first baseline、last baseline、safe、unsafe 在 flex 布局中通用性低
justify-content
justify-content 屬性定義了瀏覽器如何分配順着 flex 容器主軸的 flex 元素之間及其周圍的空間。
值 |
意義 |
---|---|
flex-start |
從行首開始排列。每行第一個 flex 元素與行首對齊,同時所有後續的 flex 元素與前一個對齊。 |
flex-end |
從行尾開始排列。每行最後一個 flex 元素與行尾對齊,其他元素將與後一個對齊。 |
center |
伸縮元素向每行中點排列。每行第一個元素到行首的距離將與每行最後一個元素到行尾的距離相同。 |
space-between |
在每行上均勻分配 flex 元素。相鄰元素間距離相同。每行第一個元素與行首對齊,每行最後一個元素與行尾對齊。 |
space-around |
在每行上均勻分配 flex 元素。相鄰元素間距離相同。每行第一個元素到行首的距離和每行最後一個元素到行尾的距離將會是相鄰元素之間距離的一半。 |
space-evenly |
flex 元素都沿着主軸均勻分佈在指定的 flex 元素中。相鄰 flex 元素之間的間距,主軸起始位置到第一個 flex 元素的間距,,主軸結束位置到最後一個 flex 元素的間距,都完全一樣。 |
語法格式
normal | <content-distribution> | <overflow-position>? [ <content-position> | left | right ] where <content-distribution> = space-between | space-around | space-evenly | stretch <overflow-position> = unsafe | safe <content-position> = center | start | end | flex-start | flex-end
baseline 值不被 React Native 支持 stretch、space-evenly、start、end、left、right、first baseline、last baseline、safe、unsafe 在 flex 布局中通用性低
place-content
place-content 屬性是 align-content 和 justify-content 的簡寫。
語法格式
<'align-content'> <'justify-content'>?
place-content 屬性是 align-content 和 justify-content 的簡寫。
- 如果第二個值不存在,且第一個值適用於用於兩者,則第二個值復用第一個
- 如果第二個值不存在,且第一個值不適用於用於兩者,則整個值無效
place-content 屬性不被 React Native 支持
Flex Item 屬性
在 Flex Item 上,同樣也有六個屬性,而 order 屬性在 React Native 上不支持。
order
order 屬性規定了 flex 容器中的 flex 元素在布局時的順序。flex 元素按照 order 屬性的值的增序進行布局。擁有相同 order 屬性值的 flex 元素按照它們在源代碼中出現的順序進行布局。默認值為 0。
語法格式
<integer>
order 屬性不被 React Native 支持
flex-grow
flex-grow 屬性定義 flex 元素的拉伸因子。
語法格式
<number> | inherit
負值無效 React Native 上默認值為 0
flex-shrink
flex-shrink 屬性指定了 flex 元素的收縮規則。flex 元素僅在默認寬度之和大於容器的時候才會發生收縮,其收縮的大小是依據 flex-shrink 的值。默認值為 1。
語法格式
<number> | inherit
負值是不被允許的。 React Native 上默認值為 1
flex-basis
flex-basis 指定了 flex 元素在主軸方向上的初始大小。如果不使用 box-sizing 改變盒模型的話,那麼這個屬性就決定了 flex 元素的內容盒(content-box)的尺寸。
注意:如果一個 flex 元素同時設置了 flex-basis (auto 除外)和 width (或者 flex-direction: column 時設置了 height ),flex-basis 權級更高。
語法規範
content | <'width'>
- <'width'>
- width 值可以是
<length>
; - 該值也可以是一個相對於其父彈性盒容器主軸尺寸的百分數 。
- 負值是不被允許的。
- 默認為 0。
- width 值可以是
- content
- 基於 flex 元素的內容自動調整大小。
React Native 上使用 ScrollView 組件會導致屬性失效 如果沒有足夠空間,組件不會發生收縮 (應該是設置了 flex-shrink 屬性值默認為 0)
flex
flex 規定了 flex 元素如何伸長或縮短以適應 flex 容器中的可用空間。這是一個簡寫屬性,用來設置 flex-grow, flex-shrink 與 flex-basis。
語法格式
none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
- initial
- 元素會根據自身寬高設置尺寸。
- 它會縮短自身以適應 flex 容器,但不會伸長並吸收 flex 容器中的額外自由空間來適應 flex 容器 。
- 相當於將屬性設置為"flex: 0 1 auto"。
- auto
- 元素會根據自身的寬度與高度來確定尺寸,但是會伸長並吸收 flex 容器中額外的自由空間,也會縮短自身來適應 flex 容器。
- 這相當於將屬性設置為 "flex: 1 1 auto"。
- none
- 元素會根據自身寬高來設置尺寸。
- 它是完全非彈性的:既不會縮短,也不會伸長來適應 flex 容器。
- 相當於將屬性設置為"flex: 0 0 auto"。
在 React Native 中只能為 number 類型
- 當 flex > 0 時,組件大小將與其彈性值成比例。因此,flex 設置為 2 的組件將佔用空間的兩倍作為 flex 設置為 1 的組件
- 當 flex = 0 時,組件根據 width 和 height 確定大小,且不會發生變化。
- 當 flex = -1 時,組件通常根據 width 和 height 確定大小。但是,如果沒有足夠的空間,組件將收縮到 minWidth 和 minHeight。
在快應用中,flex 的快捷值設置均是無效值
align-self
align-self 會對齊當前 flex 行中的 flex 元素,並覆蓋 align-items 的值. 如果任何 flex 元素的側軸方向 margin 值設置為 auto,則會忽略 align-self。
語法格式
auto | normal | stretch | <baseline-position> | <overflow-position>? <self-position> where <baseline-position> = [ first | last ]? baseline <overflow-position> = unsafe | safe <self-position> = center | start | end | self-start | self-end | flex-start | flex-end
baseline 值不被 React Native 和快應用支持 start、end、self-start、self-end、first baseline、last baseline、safe、unsafe 在 flex 布局中通用性低
組件化開發
不同的平台如 Web、React-Native、微信小程序等各有特色,平台之間的差異很大,會導致很多額外的開發成本。那麼如果我們想要完成一個跨平台項目該怎麼做呢?
我們開始從比較容易入手的方向考慮,如果採用模塊化組件或是 css-in-js 的方案去完成樣式的構建會是一個好的方案么?
在目前的前端生態中,模塊化組件開發會是個很棒的方案,覆蓋模式下構建開箱即用的組件同時可以提供方法來覆蓋樣式再好不過了,但是如果放到小程序開發的模式中,這就會有個很嚴重的問題,那就是如果我們在層級樣式表中寫到的樣式,是不能直接傳給組件來覆蓋樣式的,組件和組件的隔離在不同小程序中很難被打破。
/* CustomComp.js */ export default class CustomComp extends Component { static defaultProps = { className: '' } render () { return <View className={this.props.className}>這段文本的顏色不會由組件外的 class 決定</View> } } /* MyPage.js */ export default class MyPage extends Component { render () { return <CustomComp className="red-text" /> } }
/* MyPage.scss */ .red-text { color: red; }
如果大家嘗試上述的寫法,會發現 red-text 類中的樣式並沒有生效,那麼在這種情況下我們如果考慮是使用 css-in-js 會好么?很遺憾,如果你使用它,我們將不會為這些需要運行時處理的樣式補全前綴。
這兩個方案都不是合適的方案,那麼我們該怎麼做呢?試着去打破小程序的組件限制么?我們在微信小程序官方的文檔中找到 externalClasses 這個方法,可以先來嘗試。
不同的平台如 Web、React-Native、微信小程序等各有特色,平台之間的差異很大,會導致很多額外的開發成本。那麼如果我們想要完成一個跨平台項目該怎麼做呢?
/* CustomComp.js */ export default class CustomComp extends Component { static externalClasses = ['my-class'] render () { return <View className="my-class">這段文本的顏色由組件外的 class 決定</View> } } /* MyPage.js */ export default class MyPage extends Component { render () { return <CustomComp className="red-text" /> } }
/* MyPage.scss */ .red-text { color: red; }
但是這也並非所有的開發平台都能夠提供給開發者相關的方法,所以我們只能轉換目光到另一個 addGlobalClass 方法上,這個方法不僅在所有小程序都能夠支持,Taro 在 React Native 端上也提供了同樣的方法給大家,這樣我們也可以避開 css modules 這個體驗稍差的方法。
/* CustomComp.js */ export default class CustomComp extends Component { static options = { addGlobalClass: true } render () { return <View className="red-text">這段文本的顏色由組件外的 class 決定</View> } }
/* 組件外的樣式定義 */ .red-text { color: red; }
寫在最後
在項目中,我們已經將所有通用支持的方法寫到 scss 文件中,如果大家需要可以直接使用我們已經提供的 flexbox 樣式,按如下方法在自己全局的層級樣式表中引入我們已經提供的樣式。
@import 'https://raw.githubusercontent.com/NervJS/taro-flexbox/master/flexbox-demo/src/asset/flex.scss';
那麼關於 Flex 布局的知識,如果文中有遺漏的,大家可以跟着我們的項目來梳理知識,也可以到 MDN[1] 上查看相關的文檔,值得注意的是在 Flexbox 布局中 gap、row-gap、column-gap 屬性在 Grid 布局中普遍支持,但是在 Flex 布局中卻只有 Firefox 完成了適配,所以暫且不表,同樣 justify-content 屬性的 space-evenly 值在 web 端通用性很低,不建議使用。
gap、row-gap、column-gap in flex

gap_in_flex
space-evently on desktop

space-evently-desktop
space-evently on mobile

space-evently-mobile
屬性對照表

項目地址
項目地址:
https://github.com/NervJS/taro-flexbox
預覽地址:
https://nervjs.github.io/taro-flexbox/
預覽地址也可以點擊文章末尾的「閱讀原文」體驗 H5 版的效果
相關鏈接
- 預覽地址:https://nervjs.github.io/taro-flexbox/
- 項目地址:https://github.com/NervJS/taro-flexbox
- 參考資料:
- https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout#Reference
- https://facebook.github.io/react-native/docs/flexbox
- https://github.com/startheart/cml-flexbox 2
- [1]MDN: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout#%E5%8F%82%E8%80%83