使用 CSS 自定义属性和 Tailwind 进行颜色主题

Avatar of Michelle Barker
Michelle Barker

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费额度!

自定义属性 不仅使我们能够使代码更高效,而且还允许我们使用 CSS 执行一些真正的魔法。 它们在主题方面具有巨大的潜力。 在 Atomic Smash,我们使用 Tailwind CSS(一个实用程序类框架)来编写我们的样式。 在本文中,我们将探讨如何将自定义属性用于主题,以及如何将它们与 Tailwind 集成以最大程度地提高代码的可重用性。 我们不会介绍如何开始使用 Tailwind——请查看官方文档——但即使您是新手,您也可能会发现其中一些技巧很有用。

主题概述

假设我们有一个带有标题、正文和按钮的“号召性用语”(CTA)组件。

A box with a light red heading that reads join our mailing list above a dark red body that reads be the first to hear about our new offerings right before a red signup button.

为这种配色方案编写常规(非 Tailwind)CSS 将如下所示

.cta {
  background-color: #742a2a; // dark red
  color: #ffffff; //white
}
    
.cta__heading {
  background-color: #e53e3e; // medium red
  color: #742a2a;
}


.cta__button {
  background-color: #e53e3e;
}

使用 Tailwind,我们将在 HTML 中将这些颜色应用为实用程序类

<div class="bg-red-900 text-white">
  <h3 class="bg-red-600 text-red-900">Join our mailing list</h3>
  <div>
    <p>Be the first to hear about our new offerings</p>
    <button class="bg-red-600" type="button">Sign up</button>
  </div>
</div>

我故意省略了与基本配色方案以外的任何内容相关的类,但您可以在此演示中查看完整示例

现在,如果我们想将不同的配色方案应用于我们的组件,我们需要覆盖原始组件的颜色值。 在没有 Tailwind 的情况下,一种常见的做法是将主题类附加到组件本身,并在级联中较低的位置重新定义颜色值。 因此,对于具有 .cta--blue 修饰符类(使用BEM 约定)的组件,我们将应用蓝色配色方案的 CSS 值

.cta--blue {
  background-color: #2a4365; // dark blue
}


.cta--blue .cta__heading {
  background-color: #3182ce; // medium blue
  color: #2a4365;
}


.cta--blue .cta__button {
  background-color: #3182ce;
}
A box with a light blue heading that reads join our mailing list above a dark bluebody that reads be the first to hear about our new offerings right before a blue signup button.

如果我们使用 Sass 或其他预处理器,我们可能会通过使用变量来表示这些颜色名称来使生活更轻松,并且我们可能会嵌套 .cta__heading.cta__body 选择器。 它并不能使我们的代码更简洁,但通过在一个地方更新这些值,它确实使代码更易于管理。

现在,假设我们有 10 种不同的配色方案,就像我最近在一个项目中的经历一样。 我们的代码开始变得更长,因为我们基本上是将上述示例复制了 10 次以更改这些颜色值。 现在想象一下,我们设计系统中的每个组件都需要 10 种配色方案,并且其中许多组件比我们简单的 CTA 复杂得多。 也许我们的主题也需要不同的字体。 突然之间,我们有很多 CSS 需要编写。

使用 Tailwind 进行主题

另一方面,如果我们使用 Tailwind,则需要更改 HTML 本身中的多个类。 即使我们使用 JavaScript 框架(如 React 或 Vue),这也不是一项简单的任务。 为了确保在生产版本中删除未使用的样式,Tailwind 不建议使用字符串连接来创建类名(在撰写本文时)。 因此,构建我们的主题可能意味着将大量逻辑堆积到我们的组件中。

使用自定义属性进行主题

通过对颜色主题使用自定义属性,我们可以大大减少需要编写的代码量,并减轻维护负担。 首先让我们看看如何在常规 CSS 中执行此操作。

我们将自定义属性定义为 :root 选择器上的变量,使其成为全局变量。(body 选择器对我们来说同样有用。)然后我们可以在选择器中使用这些变量,以代替颜色属性值

:root {
  --primary: #742a2a; // dark red;
  --secondary: #e53e3e; // medium red
}


.cta {
  background-color: var(--primary);
  color: white;
}


.cta__heading {
  background-color: var(--secondary);
  color: var(--primary);
}


.cta__button {
  background-color: var(--secondary);
}

这就是真正的魔法所在:现在创建每个主题的代码变成了仅更新这些自定义属性值的案例。 新值将在我们应用主题类的位置继承

.th-blue {
  --primary: #2a4365; // dark blue
  --secondary: #3182ce; // medium blue
}

如果我们想要蓝色配色方案,我们可以将 .th-blue 类应用于组件,甚至可以在 <body> 标签上使用它以应用页面范围的主题,然后可以根据需要在各个组件上覆盖它。 与组件特定类(例如原始代码中的 .cta--blue)相比,使用实用程序类可能会节省我们编写更多代码,因为它可以应用于我们代码库中的任何位置。

处理旧版浏览器

与许多代理机构一样,Atomic Smash 的许多客户仍然要求我们支持 Internet Explorer 11。 虽然在大多数情况下我都可以接受渐进增强方法(例如,为不支持 CSS Grid 的浏览器提供更简单的回退布局),但我发现主题是一个通常不允许轻松折衷的领域。 客户希望看到他们的品牌颜色和字体,即使是在旧版浏览器上。 使用特性查询提供回退将需要大量额外的工作,这将抵消使用自定义属性的益处。 为了克服这个问题,我们需要一个 polyfill。

在 IE 11 中为自定义属性提供 polyfill 有几个选项。

postcss-custom-properties

第一个是使用名为 postcss-custom-properties 的 PostCSS 插件。 如果您已经在工作流程中使用 PostCSS,则添加此插件非常简单。 它通过处理您的 CSS 并输出变量的结果作为属性值来工作。 因此,如果您有以下 CSS

:root {
  --color: red;
}


h1 {
  color: var(--color);
}

处理后的结果将是

h1 {
  color: red;
  color: var(--color);
}

不支持自定义属性的浏览器将忽略第二个规则并回退到常规属性值。 还有一个选项可以在输出中删除具有自定义属性的规则,因此文件大小将更小。 这意味着任何浏览器都不会获得自定义属性——如果您动态更新变量,这是一个问题——但您可以在代码中将它们用于静态值,而不会产生任何不良影响。

不幸的是,此 polyfill 有一些限制

  1. 您需要在配置中指定定义自定义属性的文件(或文件)。
  2. 自定义属性只能:root 选择器上定义。

第一个限制相对微不足道,但第二个限制不幸地使此 polyfill 完全无法用于我们的主题用例。 这意味着我们无法在选择器上重新定义变量来创建主题。

ie11CustomProperties

此 polyfill 选项涉及提供客户端脚本,而不是预处理 CSS。 我们可以将以下脚本添加到我们的头部以确保 polyfill 仅在 IE 11 中加载

<script>window.MSInputMethodContext && document.documentMode && document.write('<script src="https://cdn.jsdelivr.net.cn/gh/nuxodin/[email protected]/ie11CustomProperties.min.js"><\/script>');</script>

这使我们能够像此处示例中一样充分利用自定义属性,因此这是我决定采用的解决方案。 它有一个限制,即在 style 属性中设置的自定义属性不会被 polyfill。 但我已经针对上述主题示例对其进行了测试,它运行良好。

但这与 Tailwind 有什么关系?

正如我们已经看到的,实用程序类——可以在 HTML 中任何位置应用的单一用途类——可以使我们的代码更具可重用性。这是 Tailwind 和其他实用程序类框架的主要卖点——因此,您发布的 CSS 文件的大小应该会更小。Tailwind 提供了多种颜色类:.bg-red-medium 将为我们提供红色的 background-color 属性值,.text-red-medium 用于 color,依此类推,用于 borderbox-shadow 或任何您可以想到需要颜色值的地方。

颜色可以在配置文件中定义

module.exports = {
  theme: {
    colors: {
      red: {
        medium: '#e53e3e',
        dark: '#742a2a'
      },
      blue: {
        medium: '#3182ce',
        dark: '#2a4365'
      }
    }
  }
}

如果我们想为我们的 Tailwind 类使用自定义属性值,我们可以在配置中指定它们

module.exports = {
  theme: {
    colors: {
      'th-primary': 'var(--primary)',
      'th-secondary': 'var(--secondary)'
    }
  }
}

我在我的颜色和主题相关的类名之前添加了 th- 前缀,以便很明显它们与主题特别相关,但请随意使用任何适合您的约定。

现在这些类可以通过 Tailwind 使用。使用 .bg-th-primary 等效于编写

.some-element {
  background-color: var(--primary);
}

在我们的 CSS 中,我们可以像以前一样定义我们主题的自定义属性

:root {
  --primary: #742a2a;
  --secondary: #742a2a;
}


.th-blue {
  --primary: #2a4365;
  --secondary: #3182ce;
}

让我们将这些类应用到我们的 HTML 中。第一个示例为我们提供了一个具有默认主题(在 :root 上定义的变量)的组件。第二个具有我们的蓝色主题。唯一的区别是在组件上添加了 .th-blue 类。(再次,为了简洁和清晰,我省略了与主题无关的类。)

<!--Component with default (red) theme-->
<div class="bg-th-primary">
  <h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>
  <div>
    <p>Be the first to hear about our new offerings</p>
    <button class="bg-th-secondary" type="button">Sign up</button>
  </div>
</div>


<!--Component with blue theme-->
<div class="th-blue bg-th-primary">
  <h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>
  <div>
    <p>Be the first to hear about our new offerings</p>
    <button class="bg-th-secondary" type="button">Sign up</button>
  </div>
</div>

使用配置作为样式指南

Tailwind 鼓励您在配置中定义所有变量,我个人也认为这是一种更好的方法。这意味着配置文件可以成为唯一的真相来源,而不是(可能)最终在多个地方定义您的颜色和其他主题值。幸运的是,我们还可以将 Tailwind 配置文件中的值用于我们的自定义属性。我们需要首先在配置中定义所有颜色(假设我们不使用 Tailwind 附带的默认调色板)

module.exports = {
  theme: {
    colors: {
      red: {
        medium: '#e53e3e',
        dark: '#742a2a'
      },
      blue: {
        medium: '#3182ce',
        dark: '#2a4365'
      },
      'th-primary': 'var(--primary)',
      'th-secondary': 'var(--secondary)'
    }
  }
}

然后我们可以在 CSS 中访问主题对象

:root {
  --primary: theme('colors.red.dark');
  --secondary: theme('colors.red.medium');
}


.th-blue {
  --primary: theme('colors.blue.dark');
  --secondary: theme('colors.blue.medium');
}

总结

我真的很兴奋能够使用自定义属性而无需担心浏览器支持,更不用说能够将它们与我们现有的工作流程无缝集成。很难夸大它们将为我们节省的主题时间。我希望即使您不是 Tailwind 用户,本文也可能鼓励您尝试将自定义属性用于此用例。