我們通過採用內聯式 CSS 樣式,可以獲得 Javascript 中所有程式設計支援,它的好處就像 CSS 前處理器(變數、混入和函式)一樣,而且也能解決 CSS 中的很多問題,比如全域性名稱空間和樣式衝突。
為了深入探索 Javascript 中寫 CSS 解決的問題,可以看這個著名的演示:ReactJS中引入CSS。關於 Aphrodite 上的效能改進範例,你可以在Khan Academy: Aphrodite 上查到內聯式 CSS 程式碼,如果想學習更多 Javascript 中 CSS 最佳實踐的相關知識,可以查閱 Airbnb 的樣式指南。
另外,我們使用內聯式 Javascript 樣式來建立元件,處理我前一篇文章中介紹的一些設計基本原理:在你能掌握設計之前,必須先掌握設計的基本原理。
一個啟發式的例子
我們從一個簡單的例子入手:建立和設計一個按鈕。
通常,元件 Button 和其相關的樣式 ButtonStyles 會被放入相同的檔案,這是因為它們處理的是同個問題:檢視。但是以這個例子來說,我出於多種考慮要將程式碼分開,使程式碼更易被理解。
這兒是按鈕元件:
1 2 3 4 5 6 7 8 9 10 |
<span style="color: #000000">... function Button(props) { return ( <input type="button" className={css(styles.button)} value={props.text} /> ); }</span> |
這裡沒什麼特別的——就是一個無狀態 React 元件。Aphrodite 發揮作用的地方是在 className 屬性那裡。函式 css 的作用是引入了一個styles 物件,並將其轉換為 css。styles 物件是通過 Aphrodite 的 StyleSheet.create({ … }) 語句所生成,你可以通過 Aphrodite playground 檢視 StyleSheet.create({ … }) 的輸出結果。
這兒是按鈕的樣式表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span style="color: #000000">... const gradient = 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)'; const styles = StyleSheet.create({ button: { background: gradient, borderRadius: '3px', border: 0, color: 'white', height: '48px', textTransform: 'uppercase', padding: '0 25px', boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .30)', }, });</span> |
Aphrodite 的好處之一是遷移簡單直接,而且學習曲線低緩。像 border-radius 這種屬性變成了borderRadius,值變成了字串。偽類選擇器、媒體查詢和字型定義都是可實現的,另外,會自動新增瀏覽器引擎字首。
以下是結果:
記住這個例子,我們來看看怎樣使用 Aphrodite 來建立一個基本的視覺化設計系統,我們著重於以下基本設計法則:排版和間隔。
基本法則1——排版
我們從排版入手,它是設計的重要基礎。第一步是定義排版常量,與 Sass 和 Less 不同,Aphrodite 中常量可以放在 Javascript 或 JSON 檔案中。
定義排版常量
在建立常量時,給變數起名要語義化。比如說,不要給其中的字號起名 h2,而應起像 displayLarge 這種可以描述其作用的名字。類似的,設定字型粗細時,不要給其中的粗細值起名 600,而應起像半粗體這樣的名字,方便描述其效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<span style="color: #000000">export const fontSize = { // heading displayLarge: '32px', displayMedium: '26px', displaySmall: '20px', heading: '18px', subheading: '16px', // body body: '17px', caption: '15px', }; export const fontWeight = { bold: 700, semibold: 600, normal: 400, light: 200, }; export const tagMapping = { h1: 'displayLarge', h2: 'displayMedium', h3: 'displaySmall', h4: 'heading', h5: 'subheading', }; export const lineHeight = { // heading displayLarge: '48px', displayMedium: '48px', displaySmall: '24px', heading: '24px', subheading: '24px', // body body: '24px', caption: '24px', };</span> |
對諸如字號和行高等變數正確賦值非常重要,因為它們會直接影響設計的垂直規律(vertical ryth),垂直規律能幫助實現元素之間的間隔統一。
要了解更多的垂直規律,你可以閱讀這篇文章:為什麼垂直規律是一種重要的排版習慣?
想讓行高和字號取值正確,背後大有學問。我們可以使用算數比率來產生一系列潛在尺寸以作備選。幾周前,我寫了一篇詳述方法論的文章:排版可以成就或摧毀你的設計:一個型別選擇的過程。你可以使用Modular Scale 來決定字號,使用垂直規律計算器來決定行高。
定義一個標題元件
定義了排版常量之後,下一步是建立一個元件使用其值。這個元件的目標是在程式碼庫中強化標題設計和實現的一致性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<span style="color: #000000">import React, { PropTypes } from 'react'; import { StyleSheet, css } from 'aphrodite/no-important'; import { tagMapping, fontSize, fontWeight, lineHeight } from '../styles/base/typography'; function Heading(props) { const { children, tag: Tag } = props; return <Tag className={css(styles[tagMapping[Tag]])}>{children}</Tag>; } export default Heading; export const styles = StyleSheet.create({ displayLarge: { fontSize: fontSize.displayLarge, fontWeight: fontWeight.bold, lineHeight: lineHeight.displayLarge, }, displayMedium: { fontSize: fontSize.displayMedium, fontWeight: fontWeight.normal, lineHeight: lineHeight.displayLarge, }, displaySmall: { fontSize: fontSize.displaySmall, fontWeight: fontWeight.bold, lineHeight: lineHeight.displaySmall, }, heading: { fontSize: fontSize.heading, fontWeight: fontWeight.bold, lineHeight: lineHeight.heading, }, subheading: { fontSize: fontSize.subheading, fontWeight: fontWeight.bold, lineHeight: lineHeight.subheading, }, });</span> |
Heading 是無狀態的函式式元件,它引入一個標籤作為屬性,並返回該標籤相應的樣式。因為我們早前已在常量檔案中定義了標籤對映,所以這樣是合理的。
1 2 3 4 5 6 7 8 |
<span style="color: #000000">... export const tagMapping = { h1: 'displayLarge', h2: 'displayMedium', h3: 'displaySmall', h4: 'heading', h5: 'subheading', };</span> |
我們在元件檔案的底部定義styles物件,就在這裡會用到排版常量。
1 2 3 4 5 6 7 8 9 |
<span style="color: #000000">export const styles = StyleSheet.create({ displayLarge: { fontSize: fontSize.displayLarge, fontWeight: fontWeight.bold, lineHeight: lineHeight.displayLarge, }, ... });</span> |
這就是標題元件的使用方式:
1 2 3 4 5 |
<span style="color: #000000">function Parent() { return ( <Heading tag="h2">Hello World</Heading> ); }</span> |
利用這種方式,可以減少設計系統中出現的意外變動。通過消除對全域性樣式的需求,以及對程式碼庫標題標準化,可以使我們避免各種不同字號的困擾。另外,我們用來建立標題元件的方式也可用於在程式碼主體建立文字元件。
基本法則2——間隔
間隔能夠同時控制設計中的垂直和水平規律,這使得間隔對於建立一個視覺化設計系統至關重要。和排版部分一樣,處理間隔的第一步就是定義間隔常量。
定義間隔常量
定義元素間間隔常量時,我們可以採用一種算數方法。通過使用 spacingFactor 常量可以產生一系列基於一個公因數的長度,這種方法可以確保元素間的間隔具有邏輯性和一致性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span style="color: #000000">const spacingFactor = 8; export const spacing = { space0: `${spacingFactor / 2}px`, // 4 space1: `${spacingFactor}px`, // 8 space2: `${spacingFactor * 2}px`, // 16 space3: `${spacingFactor * 3}px`, // 24 space4: `${spacingFactor * 4}px`, // 32 space5: `${spacingFactor * 5}px`, // 40 space6: `${spacingFactor * 6}px`, // 48 space8: `${spacingFactor * 8}px`, // 64 space9: `${spacingFactor * 9}px`, // 72 space13: `${spacingFactor * 13}px`, // 104 };</span> |
上面的例子用了一個從一到十三的線性刻度,但是用不同的刻度和比例做實驗。設計當中因為用途、受眾以及目標裝置的不同而需要不同的刻度。以下為例,這是值為 8 的 spacingFactor 用黃金比例算出來的前六個長度。
1 2 3 4 5 6 7 |
<span style="color: #000000">Golden Ratio (1:1.618) 8.0 x (1.618 ^ 0) = 8.000 8.0 x (1.618 ^ 1) = 12.94 8.0 x (1.618 ^ 2) = 20.94 8.0 x (1.618 ^ 3) = 33.89 8.0 x (1.618 ^ 4) = 54.82 8.0 x (1.618 ^ 5) = 88.71</span> |
這就是間隔刻度在程式碼中顯示的樣子,我加了一個輔助函式,把計算結果處理成最接近的整數畫素。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span style="color: #000000">const spacingFactor = 8; export const spacing = { space0: `${computeGoldenRatio(spacingFactor, 0)}px`, // 8 space1: `${computeGoldenRatio(spacingFactor, 1)}px`, // 13 space2: `${computeGoldenRatio(spacingFactor, 2)}px`, // 21 space3: `${computeGoldenRatio(spacingFactor, 3)}px`, // 34 space4: `${computeGoldenRatio(spacingFactor, 4)}px`, // 55 space5: `${computeGoldenRatio(spacingFactor, 5)}px`, // 89 }; function computeGoldenRatio(spacingFactor, exp) { return Math.round(spacingFactor * Math.pow(1.618, exp)); }</span> |
定義了間隔常量之後,我們就可以用它給設計中的元素加外邊距。一種方式就是在元件中引入間隔常量並使用它們。
比如,我們在 Button 元件中加入 marginBottom。
1 2 3 4 5 6 7 8 9 10 |
<span style="color: #000000">import { spacing } from '../styles/base/spacing'; ... const styles = StyleSheet.create({ button: { marginBottom: spacing.space4, // adding margin using spacing constant ... }, });</span> |
這在大多數情況下都管用,但是如果我們想根據按鈕放置的位置去改變它的 marginBottom 屬性值,該怎麼辦呢?
一種實現多種外邊距的方法是從父元件覆蓋外邊距樣式,另外一種方法是建立一個 Spacing 元件來控制元素的垂直外邊距。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span style="color: #000000">import React, { PropTypes } from 'react'; import { spacing } from '../../base/spacing'; function getSpacingSize(size) { return `space${size}`; } function Spacing(props) { return ( <div style={{ marginBottom: spacing[getSpacingSize(props.size)] }}> {props.children} </div> ); } export default Spacing;</span> |
通過這種方法,我們可以將設定外邊距的任務從子元件中抽離出來,轉而放入父元件中執行。這樣的話,子元件佈局就不得而知了,不需要知道子元件自身與其他元素之間的位置關係。
這很有用,因為一些像按鈕、輸入和卡片之類的元件可能需要多種外邊距。比如說,一個表格中的按鈕可能比一個導航欄中的按鈕需要更大的外邊距。特別告誡一點,如果一個元件的外邊距總是一致的,那麼在元件內處理外邊距會更合理。
而且你也可能已經注意到了例子中僅用了 marginBottom,這是因為在一個方向上定義所有的垂直外邊距,可以避免外邊距碰撞,並且幫你及時跟進了解設計的垂直規律。你可以在哈利羅伯特的文章《單向外邊距宣告》中看到更多這方面的內容。
最後一點,你也可以將定義的間隔常量用作內邊距。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<span style="color: #000000">import React, { PropTypes } from 'react'; import { StyleSheet, css } from 'aphrodite/no-important'; import { spacing } from '../../styles/base/spacing'; function Card(props) { return ( <div className={css(styles.card)}> {props.children} </div> ); } export default Card; export const styles = StyleSheet.create({ card: { padding: spacing.space4}, // using spacing constants as padding background: 'rgba(255, 255, 255, 1.0)', boxShadow: '0 3px 17px 2px rgba(0, 0, 0, .05)', borderRadius: '3px', }, });</span> |
通過對內外邊距使用相同的間隔常量,你便可以使自己的設計更具備視覺上的一致性。
這是結果可能出現的樣子:
既然你對 Javascript 中的 CSS 有一定掌握,那就去試試,嘗試將內聯式 Javascript 樣式納入你的下一個專案。如果能夠在單一的環境下處理所有的樣式和檢視,我想你會對此萬分感激。
針對 CSS 和 Javascript,有什麼讓你感到激動的新進展嗎?我個人因為 async/await 而感到興奮不已,你可以留言或在 Twitter 上給我發 tweet。
你可以通過 Medium 找到我,我每週都會發文章,或者關注我的 Twitter,我總在上面對設計、前端開發以及虛擬現實東拉西扯。
附言:如果你喜歡這篇文章,那麼點選 ? 並分享給朋友會很有意義。