关于如何建立动态和可配置配色方案的基础概述
在这篇文章中,我想分享关于在 CSS 中管理多种配色方案的想法。试用演示。
如果您喜欢视频,这里是这篇文章的 YouTube 版本
概述
我们将使用自定义属性和 calc()
构建一个无障碍的颜色系统,以创建一个能够适应用户偏好,同时保持最少创作体验的网页。我们从一个基本的品牌颜色开始,并从中构建一个变体系统:2 种文本颜色、4 种表面颜色和一个匹配的阴影。
本指南首先预先定义每种配色方案的所有颜色。直到最后才使用它们来更改页面。
品牌
通常,品牌颜色已经建立,并以 十六进制 或 rgb 形式交付。此 GUI 挑战赛的基本品牌颜色为 #0af
。首先,对于此颜色系统,需要将十六进制值转换为 hsl。
* {
--brand: #0af;
--brand: hsl(200 100% 50%);
}
为了实现品牌颜色变暗或变亮的概念,例如 20%,需要将 hsl 颜色值的 3 个通道提取到它们自己的自定义属性中,如下所示
* {
--brand-hue: 200;
--brand-saturation: 100%;
--brand-lightness: 50%;
}
CSS 可以对这些颜色属性进行数学运算,例如 calc(var(--brand-lightness) - 20%)
,将亮度值降低 20%。这是构建配色方案的基础,因为 CSS 可以通过调整 hsl 饱和度和亮度值,使所有颜色保持在相同的色调系列中。
浅色主题
每个颜色变体都将标有其匹配的方案,在本例中,每个都附加了 -light
。
品牌
从品牌颜色开始,通过将 --brand-hue
、--brand-saturation
和 --brand-lightness
自定义属性包装在 hsl ()
函数括号内来重建它,不进行任何计算
* {
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
}
文本颜色
接下来,配色方案的要点需要文本颜色。在浅色主题中,文本应该非常暗。请注意以下颜色的亮度如何较低,远低于 50%。
* {
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
}
--text1-light
,由于它在 10% 亮度时非常暗,因此保持了 100% 的重饱和度,以便品牌颜色仍然可以透过深海军蓝显现出来。
--text2-light
,它不如第一种颜色那么暗,这很好,因为它是一种辅助颜色,而且它的饱和度也低得多。
表面颜色
表面颜色是文本位于或嵌入其中的背景、边框和其他装饰性表面。在浅色主题中,这些是浅色,与深色的文本颜色相反。为了使用 hsl 创建浅色,我们将在第三个亮度值中使用更高的百分比值。我们还将降低饱和度,以便浅灰色看起来不太过分着色。
* {
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
}
创建了 4 种表面颜色,因为装饰颜色往往需要更多变体,用于交互时刻,例如 :focus
或 :hover
,或创建纸张层叠的外观。在这些情况下,最好在悬停时将 --surface2-light
过渡到 --surface3-light
,以便悬停导致对比度增加(亮度从 99% 降低到 92%;使其变暗)。
阴影
配色方案中的阴影是锦上添花,但为效果增添了栩栩如生的自然感,并使其从不真实的基于黑色的阴影中脱颖而出。为此,阴影的颜色将使用色调自定义属性,并略微饱和色调,但仍然非常暗。本质上是构建一个非常暗的略带蓝色的阴影。
* {
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
}
--surface-shadow-light
未包装在 hsl 函数中。这是因为 --shadow-strength
值将被组合以创建一些不透明度,并且 CSS 需要这些片段才能执行计算。跳到 rad shadow 部分 了解更多信息。
所有浅色组合在一起
无需四处寻找以了解任何浅色是如何制作的,它们都集中在 CSS 中的一个位置。
* {
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
--surface-shadow-light: var(--brand-hue) 10% calc(var(--brand-lightness) / 5);
--shadow-strength-light: .02;
}

深色主题
大多数品牌都不是从深色主题开始的,它是其主要(通常是较浅的)主题的变体。另一方面,用户通常会在不同的上下文(例如夜间)选择深色主题。这些因素使我在深色主题中牢记两件事
- 用户通常会在黑暗中使用此主题,因此请在黑暗中进行测试。
- 颜色应降低饱和度,以免因过度强烈而在屏幕上振动。
品牌
浅色主题使用未经修改的 3 个品牌 hsl 颜色通道值,而深色主题则不然。饱和度减半,亮度相对降低 50%。
* {
--brand-dark: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 2)
calc(var(--brand-lightness) / 1.5)
);
}
文本颜色
在深色主题中,文本颜色应该为浅色。以下颜色的亮度值很高,使其更接近白色。
* {
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
}
表面颜色
在深色主题中,表面颜色应该为深色。以下颜色的亮度和饱和度较低,第一个表面最暗,为 10%。
* {
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
}
阴影
在深色主题中,阴影可能很难看到。这是有道理的,因为很难使已经相当暗的东西变暗。这就是 --shadow-strength-dark
非常方便的地方,因为它允许我们通过更改一个变量来加深阴影。
* {
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
}
另外,看看阴影中的饱和度有多高。当您查看界面时,您能注意到颜色吗?尝试从开发者工具中删除饱和度,您更喜欢哪种?
所有深色组合在一起
* {
--brand-dark: hsl(var(--brand-hue) calc(var(--brand-saturation) / 2) calc(var(--brand-lightness) / 1.5));
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
}

暗淡主题
此配色方案完全是关于协调亮度和饱和度。应该存在足够的饱和度以使色调仍然可见,但也应该勉强通过 对比度分数,因为它本来就打算暗淡且低对比度。
品牌
* {
--brand-dim: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 1.25)
calc(var(--brand-lightness) / 1.25)
);
}
文本颜色
* {
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
}
表面颜色
* {
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
}
阴影
* {
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}
所有暗淡颜色组合在一起
* {
--brand-dim: hsl(var(--brand-hue) calc(var(--brand-saturation) / 1.25) calc(var(--brand-lightness) / 1.25));
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}

无障碍颜色
请注意,深色文本颜色集中最低的亮度为 65%,深色表面中最高的亮度为 25%。它们之间有 40% 的亮度喘息空间。在浅色主题中,浅色主题中有 55% 的喘息空间。保持文本和表面颜色之间的亮度差异在 40-50% 左右可以帮助保持较高的颜色对比度,同时也成为在分数较差时进行调整的微妙杠杆。
我称之为“撞击直到通过”,这是撞击亮度值直到工具显示我通过的交互。
在此挑战中创建的每个主题都通过了对比度分数。暗淡配色方案的对比度最低,但仍然通过了最低要求。为了帮助团队中的其他人使用良好的对比色,最好创建一个将表面颜色与无障碍文本颜色配对的类名。
.surface1 {
background-color: var(--surface1);
color: var(--text2);
}
.surface2 {
background-color: var(--surface2);
color: var(--text2);
}
.surface3 {
background-color: var(--surface3);
color: var(--text1);
}
.surface4 {
background-color: var(--surface4);
color: var(--text1);
}

Rad 阴影
这些主题使用一个名为 .rad-shadow
的实用程序类。此阴影是在这个 平滑阴影 工具上生成的,我非常感谢它。我采用了它生成的代码片段,并使用我自己的颜色和不透明度计算对其进行了自定义。这样做的原因是创建一个我可以在每个配色方案中调整的阴影。
为了实现这一点,我为每个配色方案创建了 2 个变量来调整,即阴影颜色和阴影强度。颜色用于饱和度和暗度调整,而强度是在深色配色方案时驱动阴影强度的简便方法。最终结果如下所示。
:root {
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
}
.rad-shadow {
box-shadow:
0 2.8px 2.2px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
0 6.7px 5.3px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .01)),
0 12.5px 10px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
0 22.3px 17.9px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
0 41.8px 33.4px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
0 100px 80px hsl(var(--surface-shadow) / var(--shadow-strength))
;
}
如果我想在我的配色方案中进一步使用阴影,我也会将阴影角度设为设计令牌常量,因为设计的所有阴影之间的光线方向应该相同。
配色方案的使用
在完成颜色的预定义后,现在是时候将它们转换为方案不可知的属性了。我的意思是,作为此配色方案项目中的 CSS 作者,应该很少需要访问特定配色方案的值。我想让您轻松地停留在主题内。
为了实现这一点,配色方案的使用应专门通过通用自定义属性来完成,我们稍后将定义这些属性。这样,使用设计变量的人永远不必担心当前设置的是哪种配色方案,他们只需要使用表面颜色和文本颜色即可。而不是 color: var(--text1-light)
,而是使用 color: var(--text1)
。所有颜色的调整和旋转都在 CSS 中更高的级别完成。
深入研究,以下代码块中的浅色主题的连接样式将通用自定义属性与浅色主题特定颜色连接起来。现在,所有对 var(--brand)
的使用都将使用浅色品牌颜色。
浅色主题(自动)
:root {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
站点现在正在使用浅色主题。这是一个非常有趣且成功的时刻!当我们使用预定义的颜色在其他配色方案上下文中时,让我们再多享受一些这样的时刻。
深色主题(自动)
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
}
浅色主题
[color-scheme="light"] {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
深色主题
[color-scheme="dark"] {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
暗淡主题
[color-scheme="dim"] {
color-scheme: dark;
--brand: var(--brand-dim);
--text1: var(--text1-dim);
--text2: var(--text2-dim);
--surface1: var(--surface1-dim);
--surface2: var(--surface2-dim);
--surface3: var(--surface3-dim);
--surface4: var(--surface4-dim);
--surface-shadow: var(--surface-shadow-dim);
--shadow-strength: var(--shadow-strength-dim);
}
此时,作者可以根据需要自由使用提供的配色方案通用属性,并且永远不必再次担心主题。
结论
现在您知道我是如何做到的了,您会怎么做呢?!🙂
让我们使我们的方法多样化,并学习在 Web 上构建的所有方法。创建一个 Codepen 或托管您自己的演示,在 Twitter 上向我发送,我会将其添加到下面的社区混音部分。
来源
社区混音 - @chris-kruining 为 no-preference
、more
和 less
添加了色调滑块、状态颜色和对比度模式:演示。