好的,各位靓仔靓女,欢迎来到今天的“JS Design Tokens:一统江湖,代码也能说人话!”专场。今天咱们不搞虚的,直接上干货,看看这 Design Tokens 到底是个啥玩意儿,怎么就能让我们的 Web 和 Mobile 应用穿上同款战袍。
开场白:设计语言的“翻译官”
想象一下,你是个设计师,精心设计了一套颜色、字体、间距方案,美滋滋地交给前端和移动端开发。结果呢?前端用 CSS 写了一堆变量,移动端 Android 用 XML 定义了一堆属性,iOS 可能直接写死在 Swift 代码里。过几天,设计改了,你得吭哧吭哧地通知所有人,让他们手动改代码。
是不是感觉头大?这就是设计语言没有统一的“翻译官”导致的。而 Design Tokens,就是这个“翻译官”。它把设计决策(颜色、字体、间距等等)抽象成平台无关的变量,然后通过工具转换成各个平台能理解的代码。
什么是 Design Tokens?
简单来说,Design Tokens 就是一些命了名的值,代表了设计决策。这些值是平台无关的,可以是颜色值、字体大小、间距、动画时长等等。
举个例子,假设我们的品牌颜色是“活力橙”,字体是“微软雅黑”,间距是 16px。用 Design Tokens 表示,可能是这样的:
{
"color": {
"brand": {
"primary": "#FFA500",
"secondary": "#FFD700"
}
},
"font": {
"family": {
"base": "Microsoft YaHei, sans-serif"
},
"size": {
"base": "16px",
"heading": "24px"
}
},
"spacing": {
"base": "16px",
"large": "32px"
}
}
这个 JSON 文件就是我们的 Design Tokens。它描述了颜色、字体、间距这些设计决策,而且是抽象的、平台无关的。我们没有说 #FFA500
是 CSS 变量还是 Android XML 属性,只是说它是“品牌主色”。
Design Tokens 的好处:
- 一致性: 确保所有平台使用相同的设计决策,避免视觉差异。
- 可维护性: 设计变更只需要修改 Design Tokens,然后重新生成各个平台的代码,不需要手动修改。
- 可扩展性: 可以轻松添加新的设计决策,或者修改现有的设计决策。
- 可复用性: 可以在不同的项目中使用相同的 Design Tokens。
- 设计和开发协作: 设计师可以使用 Design Tokens 作为沟通语言,减少歧义。
实战:如何使用 Design Tokens
接下来,我们来一步步地演示如何使用 Design Tokens。
1. 定义 Design Tokens
首先,我们需要定义我们的 Design Tokens。可以使用 JSON、YAML 等格式。
# tokens.yml
colors:
brand:
primary: "#007bff"
secondary: "#6c757d"
text:
primary: "#212529"
secondary: "#6c757d"
fonts:
family:
base: "Arial, sans-serif"
heading: "Helvetica, sans-serif"
size:
base: "16px"
heading: "24px"
spacing:
small: "8px"
medium: "16px"
large: "24px"
2. 选择转换工具
接下来,我们需要一个工具,将 Design Tokens 转换成各个平台能理解的代码。有很多选择,比如:
- Style Dictionary: 由 Amazon 开发,功能强大,支持多种平台。
- Theo: 由 Salesforce 开发,轻量级,易于使用。
- Specify: 一个设计数据平台,提供了 Design Tokens 的管理和转换功能。
这里我们选择 Style Dictionary,因为它功能强大,社区活跃。
3. 安装 Style Dictionary
首先,我们需要安装 Style Dictionary:
npm install style-dictionary --save-dev
4. 配置 Style Dictionary
我们需要创建一个配置文件,告诉 Style Dictionary 如何转换 Design Tokens。
// config.js
module.exports = {
source: ["tokens.yml"], // Design Tokens 的源文件
platforms: {
web: {
transformGroup: "css", // 使用 CSS 转换组
buildPath: "dist/web/", // 输出目录
files: [
{
destination: "variables.css", // 输出文件名
format: "css/variables", // 输出格式
options: {
showFileHeader: false,
},
},
],
},
android: {
transformGroup: "android", // 使用 Android 转换组
buildPath: "dist/android/", // 输出目录
files: [
{
destination: "colors.xml", // 输出文件名
format: "android/colors", // 输出格式
options: {
showFileHeader: false,
},
},
{
destination: "dimens.xml", // 输出文件名
format: "android/dimens", // 输出格式
options: {
showFileHeader: false,
},
},
],
},
ios: {
transformGroup: "ios", // 使用 iOS 转换组
buildPath: "dist/ios/", // 输出目录
files: [
{
destination: "Colors.swift", // 输出文件名
format: "ios/colors.swift", // 输出格式
options: {
showFileHeader: false,
},
},
{
destination: "Typography.swift", // 输出文件名
format: "ios/typography.swift", // 输出格式
options: {
showFileHeader: false,
},
},
],
},
},
};
这个配置文件告诉 Style Dictionary:
- 我们的 Design Tokens 源文件是
tokens.yml
。 - 我们需要生成 Web、Android 和 iOS 三个平台的代码。
- 对于 Web 平台,使用
css
转换组,输出 CSS 变量文件variables.css
。 - 对于 Android 平台,使用
android
转换组,输出颜色文件colors.xml
和尺寸文件dimens.xml
。 - 对于 iOS 平台,使用
ios
转换组,输出颜色文件Colors.swift
和字体文件Typography.swift
。
5. 运行 Style Dictionary
现在,我们可以运行 Style Dictionary 了:
npx style-dictionary build
运行完成后,会在 dist
目录下生成各个平台的代码。
6. 使用 Design Tokens
现在,我们可以在我们的项目中使用 Design Tokens 了。
Web (CSS):
/* dist/web/variables.css */
:root {
--color-brand-primary: #007bff;
--color-brand-secondary: #6c757d;
--color-text-primary: #212529;
--color-text-secondary: #6c757d;
--font-family-base: Arial, sans-serif;
--font-family-heading: Helvetica, sans-serif;
--font-size-base: 16px;
--font-size-heading: 24px;
--spacing-small: 8px;
--spacing-medium: 16px;
--spacing-large: 24px;
}
<style>
body {
font-family: var(--font-family-base);
font-size: var(--font-size-base);
color: var(--color-text-primary);
}
h1 {
font-family: var(--font-family-heading);
font-size: var(--font-size-heading);
color: var(--color-brand-primary);
}
.button {
background-color: var(--color-brand-primary);
color: white;
padding: var(--spacing-medium);
border-radius: 5px;
}
</style>
Android (XML):
<!-- dist/android/colors.xml -->
<resources>
<color name="color_brand_primary">#007bff</color>
<color name="color_brand_secondary">#6c757d</color>
<color name="color_text_primary">#212529</color>
<color name="color_text_secondary">#6c757d</color>
</resources>
<!-- dist/android/dimens.xml -->
<resources>
<dimen name="spacing_small">8dp</dimen>
<dimen name="spacing_medium">16dp</dimen>
<dimen name="spacing_large">24dp</dimen>
<dimen name="font_size_base">16sp</dimen>
<dimen name="font_size_heading">24sp</dimen>
</resources>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textColor="@color/color_text_primary"
android:textSize="@dimen/font_size_base"
android:padding="@dimen/spacing_medium" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me!"
android:backgroundTint="@color/color_brand_primary"
android:padding="@dimen/spacing_medium" />
iOS (Swift):
// dist/ios/Colors.swift
import UIKit
struct Colors {
static let colorBrandPrimary: UIColor = UIColor(red: 0.000, green: 0.478, blue: 1.000, alpha: 1.0)
static let colorBrandSecondary: UIColor = UIColor(red: 0.424, green: 0.463, blue: 0.490, alpha: 1.0)
static let colorTextPrimary: UIColor = UIColor(red: 0.130, green: 0.145, blue: 0.161, alpha: 1.0)
static let colorTextSecondary: UIColor = UIColor(red: 0.424, green: 0.463, blue: 0.490, alpha: 1.0)
}
// dist/ios/Typography.swift
import UIKit
struct Typography {
static let fontFamilyBase: String = "Arial, sans-serif"
static let fontFamilyHeading: String = "Helvetica, sans-serif"
static let fontSizeBase: CGFloat = 16.0
static let fontSizeHeading: CGFloat = 24.0
static let spacingSmall: CGFloat = 8.0
static let spacingMedium: CGFloat = 16.0
static let spacingLarge: CGFloat = 24.0
}
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Hello World!"
label.textColor = Colors.colorTextPrimary
label.font = UIFont(name: Typography.fontFamilyBase, size: Typography.fontSizeBase)
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
let button = UIButton(type: .system)
button.setTitle("Click Me!", for: .normal)
button.backgroundColor = Colors.colorBrandPrimary
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: Typography.fontSizeBase)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
// Auto layout constraints (example)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 50).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
}
高级用法:自定义 Transform 和 Format
Style Dictionary 提供了很多内置的 Transform 和 Format,但有时候我们需要自定义。
- Transform: 用于修改 Design Tokens 的值。比如,我们可以将颜色值从 HEX 转换为 RGB。
- Format: 用于生成特定格式的代码。比如,我们可以生成 CSS Modules 的代码。
自定义 Transform:
// config.js
module.exports = {
source: ["tokens.yml"],
transform: {
"size/pxToRem": {
type: "value",
transformer: (token) => {
if (token.unit === "pixel") {
return `${token.value / 16}rem`;
}
return token.value;
},
},
},
platforms: {
web: {
transforms: ["name/cti/kebab", "size/pxToRem", "color/hex"],
buildPath: "dist/web/",
files: [
{
destination: "variables.css",
format: "css/variables",
options: {
showFileHeader: false,
},
},
],
},
},
};
这个配置定义了一个名为 size/pxToRem
的 Transform,用于将像素值转换为 rem 值。
自定义 Format:
// config.js
module.exports = {
source: ["tokens.yml"],
format: {
"css/modules": ({ dictionary, options, file }) => {
const className = options.className || "tokens";
let output = `.${className} {n`;
dictionary.allTokens.forEach((token) => {
output += ` --${token.name}: ${token.value};n`;
});
output += "}n";
return output;
},
},
platforms: {
web: {
transforms: ["name/cti/kebab", "size/pxToRem", "color/hex"],
buildPath: "dist/web/",
files: [
{
destination: "variables.module.css",
format: "css/modules",
options: {
showFileHeader: false,
className: "my-design-tokens",
},
},
],
},
},
};
这个配置定义了一个名为 css/modules
的 Format,用于生成 CSS Modules 的代码。
Design Tokens 的最佳实践:
- 语义化命名: 使用有意义的名称,避免使用
#FFA500
这样的字面值。 - 分层: 将 Design Tokens 分成不同的层级,比如 Core Tokens、Component Tokens、Theme Tokens。
- 版本控制: 使用版本控制工具(比如 Git)管理 Design Tokens。
- 自动化: 使用自动化工具(比如 CI/CD)自动生成各个平台的代码。
- 与设计工具集成: 将 Design Tokens 与设计工具(比如 Figma、Sketch)集成,实现设计和开发的同步。
分层示例:
- Core Tokens: 定义最基础的设计决策,比如颜色、字体、间距。
- Component Tokens: 定义组件的样式,比如按钮的颜色、字体大小。
- Theme Tokens: 定义主题的样式,比如亮色主题、暗色主题。
{
"core": {
"color": {
"primary": "#007bff",
"secondary": "#6c757d"
},
"font": {
"size": {
"base": "16px"
}
},
"spacing": {
"base": "16px"
}
},
"component": {
"button": {
"primary": {
"background": "{core.color.primary}",
"text": "#fff",
"padding": "{core.spacing.base}"
}
}
},
"theme": {
"light": {
"background": "#fff",
"text": "{core.color.primary}"
},
"dark": {
"background": "#000",
"text": "#fff"
}
}
}
总结:Design Tokens,让设计和开发不再鸡同鸭讲
Design Tokens 是一个强大的工具,可以帮助我们实现设计语言的统一,提高开发效率,降低维护成本。它就像一个“翻译官”,让设计和开发不再鸡同鸭讲,而是能够用同一种语言沟通。
所以,赶紧用起来吧!让你的 Web 和 Mobile 应用穿上同款战袍,走向人生巅峰!
今天就讲到这里,感谢各位的收听!咱们下期再见!