JavaScript 中的www.2003.com CSS:基于组件的样式的未

2019-05-01 作者:计算机教程   |   浏览(146)

JavaScript 中的 CSS:基于组件的样式的未来

2017/12/03 · CSS · 样式组件

本文由 伯乐在线 - dimple11 翻译,刘唱 校稿。未经许可,禁止转载!
英文出处:Jonathan Z. White。欢迎加入翻译组

 

www.2003.com 1

 

 

 

 

 

 

 

 

 

我们通过采用内联式 CSS 样式,可以获得 Javascript 中所有编程支持,它的好处就像 CSS 预处理器(变量、混入和函数)一样,而且也能解决 CSS 中的很多问题,比如全局命名空间和样式冲突。

为了深入探索 Javascript 中写 CSS 解决的问题,可以看这个著名的演示:ReactJS中引入CSS。关于 Aphrodite 上的性能改进范例,你可以在Khan Academy: Aphrodite 上查到内联式 CSS 代码,如果想学习更多 Javascript 中 CSS 最佳实践的相关知识,可以查阅 Airbnb 的样式指南

另外,我们使用内联式 Javascript 样式来建立组件,处理我前一篇文章中介绍的一些设计基本原理:在你能掌握设计之前,必须先掌握设计的基本原理

一个启发式的例子

我们从一个简单的例子入手:创建和设计一个按钮。

通常,组件 Button 和其相关的样式 ButtonStyles 会被放入相同的文件,这是因为它们处理的是同个问题:视图。但是以这个例子来说,我出于多种考虑要将代码分开,使代码更易被理解。

这儿是按钮组件:

<span style="color: #000000">... function Button(props) { return ( <input type="button" className={css(styles.button)} value={props.text} /> ); }</span>

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({ … }) 的输出结果。

这儿是按钮的样式表:

CSS

<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>

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,值变成了字符串。伪类选择器、媒体查询和字体定义都是可实现的,另外,会自动添加浏览器引擎前缀。

以下是结果:

www.2003.com 2

 

 

 

 

 

 

 

 

记住这个例子,我们来看看怎样使用 Aphrodite 来建立一个基本的可视化设计系统,我们着重于以下基本设计法则:排版和间隔。

基本法则1——排版

我们从排版入手,它是设计的重要基础。第一步是定义排版常量,与 Sass 和 Less 不同,Aphrodite 中常量可以放在 Javascript 或 JSON 文件中。

定义排版常量

在创建常量时,给变量起名要语义化。比如说,不要给其中的字号起名 h2,而应起像 displayLarge 这种可以描述其作用的名字。类似的,设置字体粗细时,不要给其中的粗细值起名 600,而应起像半粗体这样的名字,方便描述其效果。

CSS

<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>

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),垂直规律能帮助实现元素之间的间隔统一。

要了解更多的垂直规律,你可以阅读这篇文章:为什么垂直规律是一种重要的排版习惯

www.2003.com 3

用计算器确定行高

想让行高和字号取值正确,背后大有学问。我们可以使用算数比率来产生一系列潜在尺寸以作备选。几周前,我写了一篇详述方法论的文章:排版可以成就或摧毁你的设计:一个类型选择的过程。你可以使用Modular Scale 来决定字号,使用垂直规律计算器来决定行高。

定义一个标题组件

定义了排版常量之后,下一步是创建一个组件使用其值。这个组件的目标是在代码库中强化标题设计和实现的一致性。

<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>

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 是无状态的函数式组件,它引入一个标签作为属性,并返回该标签相应的样式。因为我们早前已在常量文件中定义了标签映射,所以这样是合理的。

<span style="color: #000000">... export const tagMapping = { h1: 'displayLarge', h2: 'displayMedium', h3: 'displaySmall', h4: 'heading', h5: 'subheading', };</span>

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对象,就在这里会用到排版常量。

<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
6
7
8
9
<span style="color: #000000">export const styles = StyleSheet.create({
  displayLarge: {
    fontSize: fontSize.displayLarge,
    fontWeight: fontWeight.bold,
    lineHeight: lineHeight.displayLarge,
  },
  
  ...
});</span>

这就是标题组件的使用方式:

<span style="color: #000000">function Parent() { return ( <Heading tag="h2">Hello World</Heading> ); }</span>

1
2
3
4
5
<span style="color: #000000">function Parent() {
  return (
    <Heading tag="h2">Hello World</Heading>
  );
}</span>

利用这种方式,可以减少设计系统中出现的意外变动。通过消除对全局样式的需求,以及对代码库标题标准化,可以使我们避免各种不同字号的困扰。另外,我们用来建立标题组件的方式也可用于在代码主体建立文本组件。

基本法则2——间隔

间隔能够同时控制设计中的垂直和水平规律,这使得间隔对于建立一个可视化设计系统至关重要。和排版部分一样,处理间隔的第一步就是定义间隔常量。

定义间隔常量

定义元素间间隔常量时,我们可以采用一种算数方法。通过使用 spacingFactor 常量可以产生一系列基于一个公因数的长度,这种方法可以确保元素间的间隔具有逻辑性和一致性。

<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>

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 用黄金比例算出来的前六个长度

<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
<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>

这就是间隔刻度在代码中显示的样子,我加了一个辅助函数,把计算结果处理成最接近的整数像素。

<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>

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。

<span style="color: #000000">import { spacing } from '../styles/base/spacing'; ... const styles = StyleSheet.create({ button: { marginBottom: spacing.space4, // adding margin using spacing constant ... }, });</span>

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 组件来控制元素的垂直外边距。

<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>

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,这是因为在一个方向上定义所有的垂直外边距,可以避免外边距碰撞,并且帮你及时跟进了解设计的垂直规律。你可以在哈利罗伯特的文章《单向外边距声明》中看到更多这方面的内容。

最后一点,你也可以将定义的间隔常量用作内边距。

<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>

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>

通过对内外边距使用相同的间隔常量,你便可以使自己的设计更具备视觉上的一致性。

这是结果可能出现的样子:

www.2003.com 4

 

 

 

 

 

 

 

既然你对 Javascript 中的 CSS 有一定掌握,那就去试试,尝试将内联式 Javascript 样式纳入你的下一个项目。如果能够在单一的环境下处理所有的样式和视图,我想你会对此万分感激。

针对 CSS 和 Javascript,有什么让你感到激动的新进展吗?我个人因为 async/await 而感到兴奋不已,你可以留言或在 Twitter 上给我发 tweet。

你可以通过 Medium 找到我,我每周都会发文章,或者关注我的 Twitter,我总在上面对设计、前端开发以及虚拟现实东拉西扯。

附言:如果你喜欢这篇文章,那么点击

本文由www.2003.com发布于计算机教程,转载请注明出处:JavaScript 中的www.2003.com CSS:基于组件的样式的未

关键词: