worldzhao / blog

个人博客,内容在 issue 里。
416 stars 16 forks source link

使用 CSS 变量进行 TailwindCSS 移动端适配 #20

Open worldzhao opened 3 months ago

worldzhao commented 3 months ago

引言

在将设计师提供的字体、字号以及颜色等规范集成到项目中后,我们开始享受使用 TailwindCSS 编写样式的便利。无需频繁在 HTML 与 CSS 之间切换,不用纠结命名问题,也不会产生各种奇怪的魔法字段。然而,在使用过程中也会遇到一些小问题。

本文不做 TailwindCSS 的推广,仅从日常使用过程中遇到的一些场景提出解决方案。关于使用 TailwindCSS 的好处,可以阅读 Why Tailwind CSS 一文。

前置知识

新手开发者在上手 TailwindCSS 时,通常会被告知一个基本规则:在 TailwindCSS 体系中,间距或宽高的基本单位为 4px。例如,mr-1 等价于 margin-right: 4px

在遇到设计稿上右边距为 16px 时,会下意识地除以 4 得到 mr-4

编译结果

笔者项目增加了 tw- 前缀,无需关注

通过编译结果可以看到,TailwindCSS 将 mr-1 编译为 margin-right: 0.25rem。在没有特殊设置的情况下,一个 Web 应用的 RootFontSize 通常是 16px,这就形成了上述的映射规则。

由此可知,TailwindCSS 的基本单位是 rem,通常配合默认的根节点字号 16px 使用。

但是大多数人很快就会遇到第一个问题:

在保证先前开发直觉的前提下,移动端项目该如何集成 TailwindCSS?

移动端适配

目前移动端的适配方案主要以 remvw 为主流,遵循等比缩放的原则(适应各种设备屏幕尺寸进行差异化处理当然是正确的,但在实际落地场景中,这往往不是研发人员能够轻易决定的)。

当新建一个移动端项目时,接入通常非常简单:

  1. 将 RootFontSize 配置为 16px
  2. 注入类似于 flexible.js 的脚本,随屏幕宽度变化等比设置 RootFontSize。
  3. 配置好 px2rem 等 PostCSS 插件,将代码中的 px 单位转为合适的 rem 单位。
  4. 接入 TailwindCSS。

如此一来,与正常的 PC 端项目类似,但往往没有那么多新项目给你去接入,更多的场景是已有的一个移动端项目去接入。这些项目使用的自适应方案也不尽相同,比如有的项目大概率 RootFontSize 是 100px(方便开发人员根据视觉稿手写 rem),同时没有接入 px2rem 等插件,也有的项目直接使用 vw 布局。这些不同的情况需要一个统一的方案去适配。

同时还有一个问题,若同一个应用既有 PC 端也有移动端,构建时该如何区分?

此时 mr-1 构建结果为 margin-right: 0.25rem,只要移动端的 RootFontSize 不为 16px,那么切换至移动端必然会导致样式错乱。

其实解决方案也很简单,那就是将构建时单位移至运行时确定,引入 CSS 变量即可

解决方案

见以下函数:

module.exports = (unit = "--tpx") => {
  const convert = (value) => `calc(${16 * value} * var(${unit}))`;
  return {
    spacing: () => ({
      ...Array.from({ length: 96 }, (_, index) => index * 0.5)
        .filter((i) => i)
        .reduce((acc, i) => ({ ...acc, [i]: `${convert(i / 4)}` }), {}),
    }),
    lineHeight: {
      3: `${convert(0.75)}` /* 12px */,
      3.25: `${convert(0.8125)}` /* 13px */,
      4: `${convert(1)}` /* 16px */,
      4.5: `${convert(1.125)}` /* 18px */,
      5: `${convert(1.25)}` /* 20px */,
      6: `${convert(1.5)}` /* 24px */,
      6.5: `${convert(1.625)}` /* 26px */,
      7: `${convert(1.75)}` /* 28px */,
      8: `${convert(2)}` /* 32px */,
      9: `${convert(2.25)}` /* 36px */,
      10: `${convert(2.5)}` /* 40px */,
      10.5: `${convert(2.625)}` /* 42px */,
    },
    borderRadius: {
      sm: `${convert(0.125)}` /* 2px */,
      DEFAULT: `${convert(0.25)}` /* 4px */,
      md: `${convert(0.375)}` /* 6px */,
      lg: `${convert(0.5)}` /* 8px */,
      xl: `${convert(0.75)}` /* 12px */,
      "2xl": `${convert(1)}` /* 16px */,
      "3xl": `${convert(1.5)}` /* 24px */,
    },
    minWidth: (theme) => ({
      ...theme("spacing"),
    }),
    maxWidth: (theme) => ({
      ...theme("spacing"),
      0: "0rem",
      xs: `${convert(20)}` /* 320px */,
      sm: `${convert(24)}` /* 384px */,
      md: `${convert(28)}` /* 448px */,
      lg: `${convert(32)}` /* 512px */,
      xl: `${convert(36)}` /* 576px */,
      "2xl": `${convert(42)}` /* 672px */,
      "3xl": `${convert(48)}` /* 768px */,
      "4xl": `${convert(56)}` /* 896px */,
      "5xl": `${convert(64)}` /* 1024px */,
      "6xl": `${convert(72)}` /* 1152px */,
      "7xl": `${convert(80)}` /* 1280px */,
    }),
  };
};

tpx css 变量

在这个函数中,我们将 mr-1 转换为 calc(4 * var(--tpx))。只要在标准设备尺寸下将 tpx 始终保持为 1px 的大小,我们就可以继续沿用 TailwindCSS 的“1 个基本单位为 4px”的规则。

以下是一些具体案例:

随着屏幕尺寸的变化,RootFontSize 或 vw 也会随之变化,因此 tpx 也会相应调整(因为使用了 rem),从而实现移动端的等比缩放。

使用 CSS 变量还可以为不同的区域应用不同的适配规则,只需在对应组件根元素节点上覆盖 tpx 变量即可。

例如,在使用 html2canvas 进行屏幕截图时,可以单独渲染截图区域,并将其中的基本单位设置为 2px,即 style="--tpx:2px",可以直接达到渲染两倍图的效果,同时避免使用 rem 导致的一些问题。在这种情况下,推荐使用 PostCSS 插件将所有的 px 也转为 --tpx,以确保脱离 TailwindCSS 上下文的样式也能响应该环境变量。

总结

通过在 TailwindCSS 构建环节中引入 CSS 变量,我们可以轻松实现以下目标:

  1. 同时适配 PC 端和移动端。
  2. 为不同的区域应用不同的适配规则。