配色方案选择器






4.87/5 (49投票s)
“世界上的色彩日复一日地改变着!” - 《悲惨世界》
引言
本文并非关于代码(稍后会有代码,但重要的是解释)。我将尝试阐述软件开发的另一个方面——设计……不是软件设计,而是 UI/UX 设计。我不会涵盖 UI/UX 的全部主题(如大小、位置、形状等),只关注为您的网站选择颜色……
注意:如果您问,为什么是网站而不是桌面(原生)应用程序,我的回答是……当你创建一个桌面(原生)应用程序时,你不会玩弄颜色,而是使用操作系统提供的颜色主题——由最终用户选择,因为你想让他们感到舒适,就像在家一样。
当你在一家大公司工作时,你永远不会触及这个话题,设计师会给你确切的 UI/UX 参数,包括颜色。但是!在小公司或你独自工作的情况下,了解如何选择能够和谐搭配的颜色非常重要……
背景
我的哥哥是一位专业的平面设计师,他设计过多种语言的书籍(版式和封面)。多年来,他完成了数千个设计,他的观点是“美在于观者的眼中”。在我与他一起工作的这些年里,他教会了我许多他的小秘诀,但很明显,虽然我能够监督排版(现在大部分由计算机完成),但我完全没有能力处理颜色。
这时,我亲爱的哥哥透露,有数学方法可以用来选择颜色,而且这些方法甚至被设计师使用……我将解释这些方法,但在此之前,先简单介绍一下常用的不同颜色模型……
颜色模型
颜色模型是将现实世界的颜色抽象为由通常包含 3 或 4 个值的元组。
RGB 模型
人类视网膜(眼睛)包含三种视锥细胞(传感器),它们可以感知(看到)不同的波长,即长(**红**色)、中(**绿**色)和短(**蓝**色)。这种结构——早在电子设备时代之前就已为人所知——被用作 RGB 颜色模型的基础,该模型通过添加这三个分量来创建颜色,就像人眼一样。该模型被用于我们今天知道的所有屏幕设备——所有你的 PC、平板电脑和智能手机屏幕。你之所以熟悉它,是因为所有主要(以及大多数次要)的语言/框架都支持这种颜色模型……
然而,RGB 模型的工作方式并非人类理解颜色。例如,名为洋红色的颜色由 R:255、G:0、B:255 组成。通过增加绿色,人们应该期望看到更绿色的颜色,然而,随着绿色的不断增加,颜色却变得越来越像白色……

红色和蓝色固定为 255,绿色从 0 到 255(从左到右)
(基于 SharkD - Wikimedia Commons 的作品)
HSV 模型
该颜色模型创建于 20 世纪 70 年代,专门为计算机图形的需求而设计。该模型仍然以加法方式使用 3 个分量,但重新组织了 RGB 颜色空间,使其更容易被人理解。它还类似于艺术家使用的经典色轮,这也使得使用该模型更容易选择颜色。
基本元素是**色相**(Hue),它围绕色轮旋转,0 度是红色,360 度也是红色。**饱和度**(Saturation)定义了颜色的纯度,0 是最纯净的,100 表示颜色饱和到变成暗淡的白色。**明度**(Value)定义了颜色的亮度,0 表示黑色(缺乏光线),100 表示当前颜色(色相)最浅的色调。
这个模型非常容易理解,因为改变一个分量,你就能看到预期的变化,而不是未知的颜色变化……因此,这个颜色模型几乎专门用于所有图形程序和所有设计师……
(基于 SharkD - Wikimedia Commons 的作品)
Alpha 通道
**Alpha** 通道于 20 世纪 80 年代后半期引入计算机图形学。Alpha 通道表示颜色的透明度,100% 是完全不透明的颜色,0% 是完全透明的颜色。这个 A 元素可以应用于我们讨论的两种颜色模型……
注意:在接下来的颜色集中,我没有使用 Alpha 通道,它在这里只是为了完整性……
选择颜色集
基本思想是使用更少的颜色。环顾四周,你会发现大多数设计精良的网站使用的颜色都非常少(例如,HP 使用 4 种颜色——包括白色)。即使需要更多颜色,也是精心选择的。由于我们(至少大部分人)缺乏选择协调的颜色的天赋,我们可以使用这些基于设计师经验形成的算法……
所有这些公式都基于这样一个想法:你选择一种颜色,然后得到一套能够和谐搭配的颜色(选择的颜色是其中的一种)。
我现在将解释每个公式如何工作,以及它们各自的优缺点。
互补色
此方法创建一个包含两种颜色的集合,其中第二种颜色位于色轮的对面。最好选择冷暖色对(如橙色-蓝色或洋红色-绿色)以获得更好的对比度。无论如何,重要的是选择一种占主导地位的颜色。
优点:此方法可在颜色集中产生高对比度。
缺点:可能难以创建平衡的颜色集,尤其是在使用去饱和颜色作为基础时(去饱和颜色比普通颜色更灰/白)。
分裂互补色
此方法创建一个包含三种颜色的集合,其中另外两种颜色位于互补色的两侧(与互补色的实际距离可能是最终结果的重要因素)。与互补色方法一样,最好选择冷暖色对以获得更好的结果,并将距离保持在 15 到 45 度之间……
优点:此方法可在颜色集中产生高对比度,并比互补色方法提供更多细微差别(更多颜色!)。
缺点:可能难以创建平衡的颜色集,尤其是在使用去饱和颜色作为基础时。
三色组
此方法创建一个在色轮上均匀分布的三种颜色的集合。
优点:集合中的颜色非常和谐,但仍具有良好的对比度。
缺点:对比度低于互补色或分裂互补色。
四色组
此方法创建一个在色轮上均匀分布的四种颜色的集合。实际上,这是互补色的“双倍”,具有三色组的和谐性。
优点:集合中的颜色非常和谐,但每对颜色都具有互补色方法带来的对比度。
缺点:由于颜色数量较多,更难平衡。
五色组
此方法创建一个在色轮上均匀分布的五种颜色的集合。
优点:集合中的颜色非常和谐(如同四色组),但对比度低于之前的集合。
缺点:由于颜色数量较多,更难平衡。
邻近色
此方法创建一个包含三种颜色的集合,其中另外两种颜色紧邻原始颜色(与原始颜色的实际距离可以是最终结果的重要因素)。最好避免集合中使用冷暖色(不要选择位于冷暖色区域边界的颜色),并将距离保持在 15 到 45 度之间。
优点:这是一个丰富且易于创建的颜色集。
缺点:集合中对比度不高。
单色
此方法创建一个包含五种颜色的集合,其中新颜色是原始颜色的不同色调,通过改变颜色的饱和度来实现。这五种颜色在饱和度轴上均匀分布(在色轮上从内到外)。
优点:非常容易创建平衡且和谐的集合。
缺点:缺乏任何颜色对比度。
组合
此方法创建一个包含 25 种颜色的集合。这些颜色是通过依次应用五色组方法和单色方法组合而成的。该方法结合了单色的平衡性和五色组的对比度。
优点:集合在主颜色和子集之间都非常和谐。可用于大型网站,通过颜色区分网站的不同主题。
缺点:由于颜色数量较多,更难平衡。
色盲
2% 到 8% 的人患有某种形式的色盲。色盲是由眼睛中缺失或功能失调的一种或多种视锥细胞引起的。患有此病症的人可能无法区分颜色,会混淆颜色,或者只能看到相同颜色的不同深浅。
对于这些人来说,仅靠颜色信息是不够的——在设计网站时,将颜色信息与形状、图标和/或文本配对,以提供更好的用户体验……
![]() | ![]() |
全色盲
为盲人设计网站远远超出了本文的范围(我们谈论的是颜色——盲人没有颜色!)。如果您接到一个要求为盲人设计项目的任务,您需要找到该领域的专家(即使在设计师中这也是一项专业知识)。
代码部分
我曾承诺不写代码,但毕竟这是一个编程网站……
在这一部分,我将展示我们所见过的想法的一种可能实现的 Kern。我在这里展示的示例代码**不是**附带在文章中的代码!附加的代码是一个 Visual Studio 扩展,它允许您创建上述颜色集并将单个颜色插入到您的代码中……这里的代码仅仅是一个示例,它只显示了与本文相关的部分(如何在 WPF 中创建颜色调色板不是我们的主题 :-))
RGB 与 HSV 相互转换
如前所述,RGB 颜色模型几乎被所有语言/框架支持。在 .NET 中,我们有 `Color` 类,它基于 RGB 模型表示颜色,问题在于颜色集是围绕 HSV 色轮计算的,因此我们必须能够在这两个模型之间进行双向转换。
当然,这些计算不是商业秘密,可以在网上找到……
RGB 转 HSV
HSV 转 RGB
以及这些公式在 C# 中的一种可能实现
public class ColorEx
{
public static short MaxHue = 360;
public static short MaxSaturation = 100;
private bool _IsRGBDirty = false;
private bool _IsHSVDirty = false;
public ColorEx ( )
{
RGB2HSV( );
}
public ColorEx Clone ( )
{
return ( new ColorEx( )
{
R = R,
G = G,
B = B
} );
}
#region Properties
private Color _Color = new Color( )
{
A = 255,
R = 128,
G = 128,
B = 128
};
public Color Color
{
get
{
if ( _IsRGBDirty )
{
HSV2RGB( );
}
return ( _Color );
}
}
public byte R
{
get
{
if ( _IsRGBDirty )
{
HSV2RGB( );
}
return ( _Color.R );
}
set
{
_Color.R = value;
_IsHSVDirty = true;
}
}
public byte G
{
get
{
if ( _IsRGBDirty )
{
HSV2RGB( );
}
return ( _Color.G );
}
set
{
_Color.G = value;
_IsHSVDirty = true;
}
}
public byte B
{
get
{
if ( _IsRGBDirty )
{
HSV2RGB( );
}
return ( _Color.B );
}
set
{
_Color.B = value;
_IsHSVDirty = true;
}
}
private short _H = 0;
public short H
{
get
{
if ( _IsHSVDirty )
{
RGB2HSV( );
}
return ( _H );
}
set
{
// Hue is circular (degree)
_H = ( short )( ( value < 0 ? 360 : 0 ) + ( value % 360 ) );
_IsRGBDirty = true;
}
}
private byte _S = 0;
public byte S
{
get
{
if ( _IsHSVDirty )
{
RGB2HSV( );
}
return ( _S );
}
set
{
if ( value >= 0 && value <= 100 )
{
_S = value;
_IsRGBDirty = true;
}
}
}
private byte _V = 0;
public byte V
{
get
{
if ( _IsHSVDirty )
{
RGB2HSV( );
}
return ( _V );
}
set
{
if ( value >= 0 && value <= 100 )
{
_V = value;
_IsRGBDirty = true;
}
}
}
#endregion
#region Helpers
private void RGB2HSV ( )
{
double nR = _Color.R / ( double )255;
double nG = _Color.G / ( double )255;
double nB = _Color.B / ( double )255;
double nCmax = Math.Max( nR, Math.Max( nG, nB ) );
double nCmin = Math.Min( nR, Math.Min( nG, nB ) );
double nDelta = nCmax - nCmin;
double nH = 0;
double nS = 0;
double nV = nCmax;
if ( nDelta != 0 )
{
if ( nCmax == nR )
{
nH = ( ( nG - nB ) / nDelta ) % 6.0;
}
else if ( nCmax == nG )
{
nH = ( ( nB - nR ) / nDelta ) + 2.0;
}
else if ( nCmax == nB )
{
nH = ( ( nR - nG ) / nDelta ) + 4.0;
}
}
nH *= 60.0;
if ( nH < 0 )
{
nH += 360;
}
if ( nDelta != 0 )
{
nS = nDelta / nCmax;
}
nS *= ( double )100;
nV *= ( double )100;
_H = ( short )( nH );
_S = ( byte )( nS );
_V = ( byte )( nV );
_IsHSVDirty = false;
}
private void HSV2RGB ( )
{
double nS = _S / ( double )100;
double nV = _V / ( double )100;
double nDelta = nV * nS;
double nH = _H / 60.0;
double nX = nDelta * ( 1 - Math.Abs( ( nH % 2 ) - 1 ) );
double nM = nV - nDelta;
double nR = 0;
double nG = 0;
double nB = 0;
if ( nH >= 0 && nH < 1 )
{
nR = nDelta;
nG = nX;
}
else if ( nH >= 1 && nH < 2 )
{
nR = nX;
nG = nDelta;
}
else if ( nH >= 2 && nH < 3 )
{
nG = nDelta;
nB = nX;
}
else if ( nH >= 3 && nH < 4 )
{
nG = nX;
nB = nDelta;
}
else if ( nH >= 4 && nH < 5 )
{
nR = nX;
nB = nDelta;
}
else
{
nR = nDelta;
nB = nX;
}
nR += nM;
nG += nM;
nB += nM;
nR *= 255;
nG *= 255;
nB *= 266;
_Color.R = ( byte )( nR );
_Color.G = ( byte )( nG );
_Color.B = ( byte )( nB );
_IsRGBDirty = false;
}
#endregion
}
此代码创建了一个新的颜色类,它同时支持 RGB 和 HSV,并且始终保持同步……你在这段代码中最需要注意的一点是 H 是循环的,因为它以度为单位测量,所以 361 和 1 是相同的……
计算颜色集
三色组
除单色外,所有颜色集都通过围绕当前色轮移动原始颜色来创建集合,所以首先让我们看一个示例……
private void Triads ( ColorEx BaseColor ) { ColorEx oTriad1 = BaseColor.Clone( ); ColorEx oTriad2 = BaseColor.Clone( ); oTriad1.H += 120; oTriad2.H -= 120; // ... }
由于三色组用于创建在色轮上均匀分布的 3 种颜色,我们使用 120° 的值(\(360^{\circ}\div 3 = 120^{\circ}\)),但所有其他色相的操纵都做同样的事情,只是计算的值根据集合而定……
单色
唯一通过操纵原始颜色的饱和度来创建颜色的集合……
private void Monochromatics ( ColorEx BaseColor )
{
byte nGap = 20;
byte nS = BaseColor.S % nGap;
ColorEx oMonochromatic0 = new ColorEx( ) { H = BaseColor.H, S = nS, V = BaseColor.V };
ColorEx oMonochromatic1 = new ColorEx( ) { H = BaseColor.H, S = ( byte )( nS + nGap ), V = BaseColor.V };
ColorEx oMonochromatic2 = new ColorEx( ) { H = BaseColor.H, S = ( byte )( nS + ( 2 * nGap ) ), V = BaseColor.V };
ColorEx oMonochromatic3 = new ColorEx( ) { H = BaseColor.H, S = ( byte )( nS + ( 3 * nGap ) ), V = BaseColor.V };
ColorEx oMonochromatic4 = new ColorEx( ) { H = BaseColor.H, S = ( byte )( nS + ( 4 * nGap ) ), V = BaseColor.V };
// ...
}
这些仅仅是关于如何操纵颜色值以获得集合的示例——在附加代码中,您将找到创建本文中所有颜色集的具体代码……
摘要
如果您想作为一名 Web 开发人员交付高质量的工作,那么您的代码能够正常运行且编写良好是远远不够的——您还必须为您的网站提供完美的视觉效果和感觉。如果您是独自一人,刚开始您的职业生涯,那么为小型网站聘请专业设计师可能会非常昂贵。本文可以帮助您选择正确的颜色……
简而言之
所以。您已经确信了颜色的重要性,但仍然不想弄乱代码!
您可以下载并安装基于本文的 Visual Studio 扩展——它是免费的!