[譯] JavaScript 中的 CSS:基於元件的樣式的未來

Bamboo發表於2019-02-18

JavaScript 中的 CSS:基於元件的樣式的未來

[譯] JavaScript 中的 CSS:基於元件的樣式的未來

圖片所屬 @jonathanzwhite

使用行內樣式使我們可以獲得 JavaScript 的所有程式設計支援。這讓我們獲得類似 CSS 前處理器(變數、混入和函式)的好處,它也解決了 CSS 的很多問題,如全域性名稱空間和樣式衝突。

如果想要更深入瞭解 JavaScript 中的 CSS 所解決的問題,可以檢視著名的演示幻燈:React:JS 中的 CSS。有關使用 Aphrodite 效能優化的案例研究,你可以閱讀 行內 CSS 在可汗學院:Aphrodite。如果想要學習更多有關 JavaScript 中的 CSS 的最佳實踐,可以閱讀 Airbnb 的風格指南

此外,我們將使用行內 JavaScript 樣式來構建元件,以解決我之前的一篇文章(掌握設計之前,必須掌握基本原理)中涉及的一些基礎設計問題。

一個啟發性的例子

讓我們從一個簡單的例子開始:構建一個按鈕並給它新增樣式。

一般來說,元件及其樣式在同一個檔案中:ButtonButtonStyles。這是因為他們都屬於檢視層。但是,下面的例子中,我將程式碼拆分成多個程式碼片段,以便更容易理解。

下面就是按鈕元件:

...

function Button(props) {
  return (
    <input
      type="button"
      className={css(styles.button)}
      value={props.text}
    />
  );
}複製程式碼

它沒什麼特別的,只是一個無狀態的 React 元件。Aphrodite 起作用的地方是在 className 屬性中。css 函式接受一個 styles 物件為引數並將其轉換為 cssstyles 物件是由 Aphrodite 的函式 StyleSheet.create({ ... }) 建立的,你可以用 Aphrodite playground 來檢視這個函式的輸出結果。

下面是按鈕的樣式表:

...

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)',
  },
});複製程式碼

Aphrodite 的優勢之一是遷移很直觀,學習曲線較平緩。類似 border-radius 變成 borderRadius,值變成字串,偽類選擇器、媒體查詢、字型定義都可以正常工作。另外也可以自動新增瀏覽器引擎字首。

下面就是按鈕的樣子:

[譯] JavaScript 中的 CSS:基於元件的樣式的未來

以這個例子為基礎,讓我們來看看如何使用 Aphrodite 來構建一個基本的視覺設計系統,著重關注排版和間距兩個設計基礎元素。

設計基礎第一部分:排版

我們先從排版開始,這是設計基礎要素。第一步是定義排版常數。與 Sass 及 Less 不一樣,Aphrodite 的常數可以直接放在 JavaScript 中或 JSON 檔案中。

定義排版常數

在定義常量時,使用語義化的變數名。例如,在給字型大小命名時,不要使用 h2,使用 displayLarge 描述它的作用。類似的,不要給字型粗細命名 600,使用 semibold 描述它的效果。

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',
};複製程式碼

設定正確的字型大小和行高變數的值是很重要的。這是因為他們直接影響了設計的垂直韻律。垂直韻律是一個能幫助你實現一致的元素間距的概念。

想要了解更多有關垂直韻律的內容,你可以閱讀這篇文章:為什麼垂直韻律對排版實踐很重要?

[譯] JavaScript 中的 CSS:基於元件的樣式的未來

上圖:行高計算器

選擇行高以及字型大小的背後是有科學原理的。我們可以使用比率生成一組可能的值。幾周前,我寫了一篇文章,詳細地介紹了方法細節(排版可以成就設計,也可以毀了設計)。你可以使用 Modular Scale 確定字型大小,使用 vertical rhythm calculator 計算行高。

定義標題元件

定義好了排版常量後,下一步就是使用它們建立一個元件。這個元件的目標是對整個程式碼庫中的標題實現一致的設計

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,
  },
 });複製程式碼

Heading 元件是一個無狀態的函式,接收一個標籤作為屬性,並返回這個標籤連帶它的樣式。我們在前面的常量中定義了標籤對映,所以這是可行的。

...
export const tagMapping = {
  h1: 'displayLarge',
  h2: 'displayMedium',
  h3: 'displaySmall',
  h4: 'heading',
  h5: 'subheading',
};複製程式碼

在元件檔案的下方我們定義了 styles 物件,我們就是在此處使用排版常量的。

export const styles = StyleSheet.create({
  displayLarge: {
    fontSize: fontSize.displayLarge,
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.displayLarge,
  },

  ...
});複製程式碼

Heading 元件是這樣呼叫的:

function Parent() {
  return (
    <Heading tag="h2">Hello World</Heading>
  );
}複製程式碼

通過這種方法,我們可以減少型別的意外變化。通過取消全域性樣式以及標準化標題,我們避免了上百種字型大小的問題。此外,這種方法還可以應用於構建 Text 元件。

設計基礎第二部分:間距

間距同時控制著設計中的垂直與水平韻律。所以間距對建立視覺設計系統至關重要。和排版部分一樣,第一步也是設定間距常量。

定義間距常量

當為元素之間的 margin 定義間距常量時,我們可以採取一種數學方法。使用一個 spacingFactor 常量來生成一組距離。這種方法確保元素之間的間距是有邏輯並且一致的

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
};複製程式碼

上面的例子採用了線性關係,從 1 到 13。不管怎樣,多試驗幾種不同的尺度和比例的搭配才能找到合適的方案。目的、受眾、目標裝置的不同都需要在設計時考慮。下面是使用黃金比率計算出來的前 6 個距離,以 spacingFactor 等於 8 為例。

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複製程式碼

下面是在程式碼中如何寫間距比例。我新增了一個幫助處理間距計算結果的函式,它會返回其最近的畫素值。

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));
}複製程式碼

定義好間距常量後,我們就可以用它們給元素新增間距。一種方法就是在元件中 import

例如,下面我們給 Button 元件新增 marginBottom

import { spacing } from '../styles/base/spacing';

...

const styles = StyleSheet.create({
  button: {
    marginBottom: spacing.space4, // 使用間距常量來新增 margin
    ...
  },
});複製程式碼

多數情況下這都是有效的。但是如果我們想要根據按鈕的位置來修改它的 marginBottom 屬性呢?

實現可變邊距的一種方法是覆蓋從父元件繼承的樣式。另一種方法是建立一個 Spacing 元件來控制元素的垂直邊距

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;複製程式碼

這種方法可以將設定邊距的任務從子元件轉移到父元件上。這樣,子元件就對佈局無感知了,它不需要知道將被放置在何處及與其他元素的關聯

由於按鈕、輸入框、卡片等元件可能需要可變的間距,所以這種方法是有效的。例如,表單中的按鈕可能比導航欄的按鈕需要更大的邊距。需要注意的是,如果一個元件始終具有一致的邊距,那麼在元件內部處理邊距更好。

你可能注意到前面的例子中只使用了 marginBottom ,這是因為在一個方向定義所有的垂直邊距可以避免邊距合併,並能跟蹤垂直韻律。你可以從 Harry Robert 的文章 單向邊距宣告 中瞭解更多這方面知識。

最後,你還可以使用間距常量來定義 padding。

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',
  },
});複製程式碼

對 margin 和 padding 使用相同的間距常量,可以在設計中實現更好的視覺一致性。

結果大致如下:

[譯] JavaScript 中的 CSS:基於元件的樣式的未來

現在你已經大致瞭解 JavaScript 中的 CSS 了,去試驗一下吧。嘗試在下個專案中採用行內 JavaScript 樣式吧。我想你會喜歡上能夠在同一個上下文中處理所有樣式及檢視問題的感覺

有關 CSS 和 JavaScript 的主題中,你對什麼新的發展感興趣呢?我個人對 async/await 非常感興趣。給我留言或者在 Twitter 上發資訊給我吧。

你可以在 Medium 上找到我,我每週都會發布一篇文章。你也可以在 Twitter 上關注我,我會在那裡釋出一些有關設計、前端開發和虛擬現實的隨筆。

如果你喜歡這篇文章,歡迎給我點贊 ❤ 並分享給朋友,非常感謝!

[譯] JavaScript 中的 CSS:基於元件的樣式的未來

[譯] JavaScript 中的 CSS:基於元件的樣式的未來

掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章