跨端 Design Token 的单位换算:Rem、Px 与 Dp 在 AI 转换中的坑

封面信息图

在跨平台前端工程化建设中,当团队试图用大模型将一套 Figma JSON 或 Web CSS Token 自动化转换为 iOS(Swift)、Android(Kotlin/Compose)和 Flutter 产物时,最容易引发灾难的隐藏炸弹莫过于单位换算(Unit Transformation)

很多开发者简单地以为:“px 就是写死数字,rem 就是除以 16,dp 就是直接把 px 换个名字”。

这种粗糙的认知一旦进入大模型的生成逻辑,就会导致一系列恶性 Bug:在 Android 手机上当用户开启了“系统超大字体(Accessibility Font Scaling)”时整个卡片文字被削掉一半、在 iPad 视网膜屏上线条粗细发生剧烈跳跃、或者在 Web 端使用 rem 导致图表几何尺寸发生意外形变

本文将深入拆解多端尺寸单位的底层物理机制,揭秘大模型在跨端 Token 转换中的典型陷阱,并给出工业级的无损转换规范。

全平台尺寸单位核心对照与心智模型

要实现精准转换,首先必须理清四大平台的尺寸哲学:

平台环境核心布局单位核心字体单位基准定义与缩放响应机制
Web 现代标准rem(或流式 clamprem相对根元素 <html>font-size(默认 16px)。用户调整浏览器字号时自适应缩放
Flutter 跨端逻辑像素(Logical Pixel / double逻辑像素(double1 逻辑像素 $\approx$ 1/160 英寸。框架内部根据 devicePixelRatio 自动映射为物理像素
Android 原生dp(Density-independent Pixel)sp(Scale-independent Pixel)绝对红线:文字必须且只能用 sp,才能响应系统辅助功能(A11y)的动态字体放大!
iOS / Swiftpt(Points)pt(配合 Dynamic Type)1pt 在 @2x 屏对应 2 个物理像素,在 @3x 屏对应 3 个物理像素
/* 原始 DTCG 标准输入文件:tokens/spacing.json */
{
  "spacing": {
    "card": {
      "padding": {
        "$value": "16px",
        "$type": "dimension",
        "$description": "通用卡片标准内边距"
      }
    }
  },
  "typography": {
    "body": {
      "fontSize": {
        "$value": "14px",
        "$type": "dimension",
        "$description": "常规正文字号"
      }
    }
  }
}

大模型在自动化转换中常踩的三大深坑

坑点一:在 Android 平台将字号误编译为 dp

大模型在解析 typography.body.fontSize: "14px" 时,经常直接把单位无脑替换为 14.dp

  • 灾难后果:在 Jetpack Compose 中,如果字号声明为 .dp 而非 .sp,当老年用户在系统设置里开启了“200% 大字号模式”时,所有的正文字体将被死死锁定在 14dp,完全无法放大,直接被 Google Play 无障碍审查拒审!
坑点二:在 Web 端对边框和阴影滥用 rem

一些模型为了追求所谓的“全 rem 纯洁性”,将 1px 边框强行写成了 0.0625rem

  • 灾难后果:当用户在手机端缩放网页时,0.0625rem 会在亚像素栅格化时发生四舍五入,导致在某些屏幕宽度下边框完全消失,在另一些宽度下变成粗细不均的重线。
  • 铁律布局间距(Spacing)与字号(Typography)可以用 rem,但微小边框(Border 1px/2px)与细微阴影偏移必须强制保留物理 px
坑点三:Flutter 平台生成了带单位的字符串

Flutter 的 EdgeInsetsTextStyle 接收的是纯 double 浮点数,但大模型经常输出 const padding = "16.0px"16.dp,导致 Dart 编译器直接抛出语法错误。

工业级跨端转换流水线代码实现

为了确保百分之百安全,我们应当建立严密的类型分流转换器:

// unit-transformer.ts 跨端单位转换核心逻辑
export interface TokenDimension {
  value: number; // 纯物理像素数字 (如 16)
  type: 'spacing' | 'typography' | 'border' | 'radius';
}

export class CrossPlatformUnitCompiler {
  // 编译为 Web CSS 产物
  public static toWeb(token: TokenDimension): string {
    if (token.type === 'border') {
      return `${token.value}px`; // 边框死锁 px
    }
    // 间距与字号转为标准 rem (基于 16px 基准)
    const remVal = parseFloat((token.value / 16).toFixed(4));
    return `${remVal}rem`;
  }

  // 编译为 Android Jetpack Compose (Kotlin)
  public static toAndroidCompose(token: TokenDimension): string {
    if (token.type === 'typography') {
      return `${token.value}.sp`; // 文字强制 sp!
    }
    return `${token.value}.dp`; // 其余布局强制 dp
  }

  // 编译为 Flutter (Dart)
  public static toFlutter(token: TokenDimension): string {
    // Flutter 统一使用 double 纯数字
    return `${token.value.toFixed(1)}`;
  }

  // 编译为 iOS (Swift)
  public static toSwift(token: TokenDimension): string {
    return `CGFloat(${token.value.toFixed(1)})`;
  }
}

转换后的全平台产物对比:

/* Web: build/css/tokens.css */
:root {
  --spacing-card-padding: 1rem;       /* 16px -> 1rem */
  --typography-body-font-size: 0.875rem; /* 14px -> 0.875rem */
  --border-card-width: 1px;           /* 边框严格保留 1px */
}
// Android: build/compose/Tokens.kt
object AppDesignTokens {
  val spacingCardPadding = 16.dp
  val typographyBodyFontSize = 14.sp // 完美响应无障碍缩放
  val borderCardWidth = 1.dp
}
// Flutter: build/lib/app_tokens.dart
class AppDesignTokens {
  static const double spacingCardPadding = 16.0;
  static const double typographyBodyFontSize = 14.0;
  static const double borderCardWidth = 1.0;
}

总结

跨端 Design Token 的单位换算看似只是几个后缀字母的替换,背后却连接着各大操作系统对人机工效、屏幕密度与无障碍关怀的深刻理解。建立类型化的单位防护规则,告别大模型的盲目替换,你的设计系统才能在多端编译中做到真正的像素级精准与无障碍自洽。

Logo

一站式 AI 云服务平台

更多推荐