使用 JavaScript 調整 RGB 颜色的饱和度和亮度

Avatar of Hugh Haworth
Hugh Haworth

DigitalOcean 为您的旅程的每个阶段提供云产品。 立即开始使用 $200 免费积分!

最近,我一直在研究使用颜色(或者像我所在的在新西兰那样拼写为“colour”)进行设计。 看看 Adam Wathan 和 Steve Schroger 关于该主题的建议,我们会发现,在构建应用程序时,我们需要的不仅仅是来自颜色调色板生成器的五个好看的十六进制代码。 我们需要大量的灰色和几种主要颜色。 从这些主要颜色中,我们将需要各种亮度和饱和度水平。

在开发应用程序时,我主要使用十六进制代码或 RGB 颜色,我发现试图从单一色调中找出不同程度的亮度和饱和度会让我速度变慢。 因此,为了让您不必费力地在 VS Code 中仔细移动颜色选择器,或者不断打开 hexcolortool,让我们来看看一些可以帮助您操纵这些颜色的代码。

HSL 值

使用 HSL 值编写网页颜色是一种有效的方法,尤其是在您打算手动更改颜色时。 HSL 代表色调、饱和度、亮度。 使用 HSL,您可以将色调声明为 0 到 360 之间的数字。 然后,您可以分别记下饱和度和亮度的百分比。 例如

div {
  background-color: hsl(155, 30%, 80%);
}

这将为您提供一种浅色、柔和的薄荷绿色。 如果我们需要在该 div 上放置一些深色文本怎么办? 我们可以使用接近黑色的颜色,但与背景一致。 例如,我们可以获取相同的 HSL 值并将亮度降低到 5%:

div {
  background-color: hsl(155, 30%, 80%);
  color: hsl(155, 30%, 5%);
}

不错。 现在我们有了非常接近黑色的文本,但看起来更自然,并且与它的背景相关联。 但是如果这不是一段文本,而是一个号召性用语按钮呢? 我们可以通过稍微提高背景的饱和度和降低亮度来吸引更多注意力

.call-to-action {
  background-color: hsl(155, 80%, 60%);
  color: hsl(155, 30%, 5%);
}

或者,如果有一些不那么重要的文本呢? 我们可以提高文本的亮度,并降低饱和度。 这将减少一些对比度,并允许这些不太重要的文本更多地融入背景。 也就是说,我们需要小心保持足够高的对比度,以确保可访问性和可读性,因此让我们再次淡化背景

div {
  background-color: hsl(155, 30%, 80%);
  color: hsl(155, 30%, 5%);
}

.lessimportant {
  color: hsl(155, 15%, 40%);
}

HSL 值 在所有主流浏览器中都受支持,并且与 RGB 相比,它们是定义颜色的更好方法。 这是因为它们允许您对颜色的色调、饱和度和亮度更加明确。

但是,如果您已经决定使用 RGB 值怎么办? 或者您的老板给您发了一封电子邮件,问“这会在 IE 8 上运行吗?”

有很多很棒的颜色库可以将 HSL 值转换回十六进制代码或 RGB 颜色。 它们中的大多数还具有多种操作功能,可以帮助构建配色方案。

以下是一些我认识的库列表

  • 如果在格式之间进行转换是一个问题,请尝试使用 Philipp Mildenberger 编写的 colvertize。 它是一个轻量级的库,提供了很多转换方法和一些操作方法。
  • 然后我们有 Josh Junon 维护的 color。 这使您可以使用流畅的界面声明、处理和提取颜色。 它提供了各种转换和操作方法。
  • 另一个是 Mozilla 的 Brian Grinstead 编写的 TinyColor,它可以处理很多输入类型以及实用程序函数。 它还提供了一些函数来帮助生成配色方案。

这里还有一篇 关于在 JavaScript 中转换颜色格式的优秀 CSS-Tricks 文章。

颜色网格工具

另一个选择是您可以尝试使用我创建的一个名为颜色网格的 颜色工具。 引用 重构 UI 的话,“尽管很诱人,但你不能完全依赖数学来打造完美的调色板。”

当然,读完这篇文章后,我创建了一个 React 应用程序,用数学方法打造了调色板。 好吧,它并不能解决所有问题,但它可能会为你提供一些选项。 该应用程序将根据你选择的色调创建 100 个不同的饱和度和亮度级别。 你可以点击网格项复制十六进制代码,或者从结尾处的文本区域复制颜色作为 CSS 自定义属性。 如果你需要一种快速的方法来获取一个或两个色调的变化,这可以试试。

以下是一些我学习到的关于处理 RGB 颜色的技术,如果你正在使用 RGB 颜色,并且需要一种方法来转换它们。

如何查找 RGB 颜色的亮度

免责声明: 此技术不考虑色调的内在价值。 色调的内在价值是指在开始添加任何黑色或白色之前,它固有的亮度。 它用纯黄色看起来比纯紫色亮得多来说明。

此技术根据对混合了多少白色或黑色的程序化度量来生成亮度级别。 感知亮度受多种因素影响,不仅仅是这种度量,因此请记住也要使用您的眼睛来判断您需要的亮度级别。

RGB 颜色的亮度级别可以通过找到 RGB 值中最高值和最低值的平均值,然后将其除以 255(中间颜色不会影响亮度)来计算出来。

这将为您提供一个介于零和一之间的十进制数,代表亮度。 这是一个 JavaScript 函数,用于执行此操作

function getLightnessOfRGB(rgbString) {
  // First convert to an array of integers by removing the whitespace, taking the 3rd char to the 2nd last then splitting by ','
  const rgbIntArray = (rgbString.replace(/ /g, '').slice(4, -1).split(',').map(e => parseInt(e)));


  // Get the highest and lowest out of red green and blue
  const highest = Math.max(...rgbIntArray);
  const lowest = Math.min(...rgbIntArray);


  // Return the average divided by 255
  return (highest + lowest) / 2 / 255;
}

这里有一个使用此函数的 CodePen

如何不改变亮度或色调的情况下增加 RGB 颜色的饱和度

我们如何利用新发现的查找 RGB 亮度的方法? 它可以帮助我们增加 RGB 颜色的饱和度,而不会改变亮度。

不过,增加 RGB 饱和度会遇到一些问题

  • 灰色在 RGB 格式中没有信息可以告诉我们饱和版本会是什么样子,因为灰色没有色调。 因此,如果我们要编写一个函数来增加颜色的饱和度,我们需要处理这种情况。
  • 我们实际上无法获得纯色调,除非颜色是 50% 亮度——任何其他亮度都会被黑色或白色稀释。 因此,我们有两种选择:在增加颜色的饱和度时保持相同的亮度,或者将颜色移向 50% 亮度以获得最鲜艳的版本。 在此示例中,我们将保持相同的亮度级别。

让我们从颜色 rgb(205, 228, 219) 开始——一种浅色、柔和的青色。 要增加颜色的饱和度,我们需要增加最低 RGB 值和最高 RGB 值之间的差值。 这将使其更接近纯色调。

如果我们想保持相同的亮度,我们需要将最高值增加相同的值,并将最低值减少相同的值。 但是,由于 RGB 值需要限制在 0 到 255 之间,因此当颜色更浅或更深时,我们的饱和度选项将受到限制。 这意味着对于任何给定的亮度,我们都有一段饱和度范围可用。

让我们获取我们颜色的可用饱和度范围。 我们可以通过找到以下两个值中的最小值来计算它

  • 与我们颜色亮度相同的灰色的 RGB 值与 255 之间的差值
  • 与我们颜色亮度相同的灰色的 RGB 值与 0(即灰色值本身)之间的差值

要获得与我们颜色亮度相同的完全灰色版本,我们可以获取上一节中 getLightnessOfRGB 函数的最终结果,并将其乘以 255。 然后,将此数字用于我们的三个 RGB 值,以获得与原始颜色亮度相同的灰色。

现在让我们这样做

// Using the previous "getLightnessOfRGB" function
const grayVal = getLightnessOfRGB('rgb(205, 228, 219)')*255; // 217
// So a gray version of our color would look like rgb(217,217,217);
// Now let's get the saturation range available:
const saturationRange =  Math.round(Math.min(255-grayVal,grayVal)); // 38

假设我们要将颜色的饱和度提高 50%。 为此,我们要将最高 RGB 值增加饱和度范围的 50%,并将最低 RGB 值减少饱和度范围的 50%。 但是,这可能会使我们超过 255 或低于零,因此我们需要通过这两个值中的最小值来限制更改

  • 最高 RGB 值与 255 之间的差值
  • 最低 RGB 值与 0(即值本身)之间的差值
// Get the maximum change by getting the minimum out of: 
// (255 - the highest value) OR (the lowest value)
const maxChange = Math.min(255-228, 205); // 27


// Now we will be changing our values by the lowest out of:
// (the saturation range * the increase fraction) OR (the maximum change)
const changeAmount = Math.min(saturationRange/0.5, maxChange) // 19

这意味着我们需要将最高 RGB 值(绿色)增加 19,并将最低 RGB 值减去 19

const redResult = 205 - 19; // 186
const greenResult= 228 + 19; // 247

第三个值怎么办?

这里事情变得有点复杂。 中间值与灰色的距离可以使用它与另外两个值中任何一个的灰色距离之间的比率来计算。

当我们将最高值和最低值从灰色移得更远时,中间值会按比例增加/减少。

现在,让我们获取最高值与全灰色的差值。 然后是中间值与全灰色的差值。 然后,我们将获得这两个差值的比率。 我还要从计算灰色值的四舍五入中删除四舍五入,使其更精确

const grayVal = getLightnessOfRGB('rgb(205, 228, 219)')*255;
const highDiff = grayVal - 228; // -11 subtracting green - the highest value
const midDiff = grayVal - 219; // -2 subtracting blue - the middle value
const middleValueRatio = midDiff / highDiff; // 0.21739130434782608

接下来我们需要做的是,获取新的 RGB 绿色值(在添加 19 之后)与灰色值之间的差值,然后将这个差值乘以我们的比率。我们将这个结果加回到灰色值上,这就是我们新饱和蓝色值的答案。

// 247 is the green value after we applied the saturation transformation
const newBlue = Math.round(grayVal+(247-grayVal)*middleValueRatio); // 223

因此,在应用了我们的变换之后,我们得到一个 RGB 颜色值 rgb(186, 247, 223 - 这是我们开始时的颜色的更鲜艳的版本。但是它保留了亮度和色调。

以下是一对 JavaScript 函数,它们一起工作以将颜色饱和度提高 10%。第二个函数返回一个对象数组,表示按大小顺序排列的 RGB 值。这个第二个函数在本文中的所有其他函数中都被使用。

如果你给它一个灰色,它只会返回相同的颜色。

function saturateByTenth(rgb) {
  const rgbIntArray = (rgb.replace(/ /g, '').slice(4, -1).split(',').map(e => parseInt(e)));
  const grayVal = getLightnessOfRGB(rgb)*255;
  const [lowest,middle,highest] = getLowestMiddleHighest(rgbIntArray);


  if(lowest.val===highest.val){return rgb;}
  
  const saturationRange =  Math.round(Math.min(255-grayVal,grayVal));
  const maxChange = Math.min((255-highest.val),lowest.val);
  const changeAmount = Math.min(saturationRange/10, maxChange);
  const middleValueRatio =(grayVal-middle.val)/(grayVal-highest.val);
  
  const returnArray=[];
  returnArray[highest.index]= Math.round(highest.val+changeAmount);
  returnArray[lowest.index]= Math.round(lowest.val-changeAmount);
  returnArray[middle.index]= Math.round(grayVal+(returnArray[highest.index]-grayVal)*middleValueRatio);
   return (`rgb(${[returnArray].join()})`);
}


function getLowestMiddleHighest(rgbIntArray) {
  let highest = {val:-1,index:-1};
  let lowest = {val:Infinity,index:-1};


  rgbIntArray.map((val,index)=>{
    if(val>highest.val){
      highest = {val:val,index:index};
    }
    if(val<lowest.val){
      lowest = {val:val,index:index};
    }
  });


  if(lowest.index===highest.index){
    lowest.index=highest.index+1;
  }
  
  let middle = {index: (3 - highest.index - lowest.index)};
  middle.val = rgbIntArray[middle.index];
  return [lowest,middle,highest];
}

如何降低 RGB 颜色的饱和度

如果我们完全降低颜色的饱和度,我们将得到一个灰色阴影。RGB 灰色始终具有三个相等的 RGB 值,因此我们可以直接使用上一个函数中的 grayVal 来创建一个与任何给定颜色具有相同亮度的灰色。

如果我们不想直接变成灰色,只想稍微降低颜色的饱和度怎么办?我们可以通过反转前面的例子来做到这一点。

让我们看另一个例子。如果我们从 rgb(173, 31, 104) 开始,我们有一个饱和的红色。让我们获取亮度的十进制度量值并将其乘以 255 以获得灰色版本。

const grayVal = Math.round(getLightnessOfRGB('rgb(173, 31, 104)') * 255); // 102

这意味着如果我们将此颜色完全降低饱和度变成灰色,我们将得到 rgb(102, 102, 102)。让我们将它的饱和度降低 30%。

首先,我们需要再次找到颜色的饱和度范围。

const saturationRange = Math.round(Math.min(255-grayVal,grayVal)); // 102

要将我们的颜色饱和度降低 30%,我们要将最高和最低颜色在这个范围内移动 30% 的距离,朝向全灰。但我们还需要通过这两个颜色之间的距离(最高和最低的距离将相同)和全灰之间的距离来限制变化量。

// Get the maximum change by getting the difference between the lowest (green) and the gray value
const maxChange = grayVal-31; // 71
// Now grab the value that represents 30% of our saturation range
const changeAmount = Math.min(saturationRange * 0.3, maxChange) // 30.59999

并将这个变化量添加到最低的 RGB 值中,并从最高的 RGB 值中减去它:

const newGreen =Math.Round(31+changeAmount); // 62
const newRed =Math.Round(173-changeAmount); // 142

然后使用与最后一个函数相同的比率技术来找到第三个颜色的值。

const highDiff = grayVal - 173; // -71 subtracting red - the highest value
const midDiff = grayVal - 104; // -2 subtracting blue - the middle value
const middleValueRatio = midDiff / highDiff; // 0.02816901408
const newBlue = Math.Round(grayVal+(142.4-grayVal)*middleValueRatio); // 103

这意味着将我们的红色饱和度降低 30% 后的 RGB 表示将是 rgb(142, 62, 103)。色调和亮度完全相同,但它不太鲜艳。

这是一个将颜色饱和度降低 10% 的 JavaScript 函数。它基本上是前面函数的反转。

function desaturateByTenth(rgb) {
  const rgbIntArray = (rgb.replace(/ /g, '').slice(4, -1).split(',').map(e => parseInt(e)));
  //grab the values in order of magnitude 
  //this uses the getLowestMiddleHighest function from the saturate section
  const [lowest,middle,highest] = getLowestMiddleHighest(rgbIntArray);
  const grayVal = getLightnessOfRGB(rgb) * 255;


  if(lowest.val===highest.val){return rgb;}
  
  const saturationRange =  Math.round(Math.min(255-grayVal,grayVal));
  const maxChange = grayVal-lowest.val;
  const changeAmount = Math.min(saturationRange/10, maxChange);
                               
  const middleValueRatio =(grayVal-middle.val)/(grayVal-highest.val);
  
  const returnArray=[];
  returnArray[highest.index]= Math.round(highest.val-changeAmount);
  returnArray[lowest.index]= Math.round(lowest.val+changeAmount);
  returnArray[middle.index]= Math.round(grayVal+(returnArray[highest.index]-grayVal)*middleValueRatio);
  return (`rgb(${[returnArray].join()})`);
}



这里有一个 CodePen,你可以用来尝试这些饱和度函数的效果。

如何保持色调不变地使 RGB 颜色变亮

要使 RGB 值变亮并保持相同的色调,我们需要将每个 RGB 值增加相同比例的差值,该差值是在该值与 255 之间。假设我们有这个颜色:rgb(0, 153, 255)。这是一个完全饱和的蓝色/青色。让我们看看每个 RGB 值与 255 之间的差值:

  • 红色为零,因此差值为 255。
  • 绿色为 153,因此差值为 102。
  • 蓝色为 255,因此差值为零。

现在当我们使颜色变亮时,我们需要将每个 RGB 值增加我们差值的相同分数。需要注意的是,我们实际上是在将白色混合到我们的颜色中。这意味着颜色会在变亮时逐渐失去饱和度。

让我们将此颜色的亮度增加十分之一。我们将从最低的 RGB 值,红色开始。我们将 255 的十分之一添加到这个值中。我们还需要使用 Math.min 来确保该值不会超过 255。

const red = 0;
const newRed = Math.round( red + Math.min( 255-red, 25.5 )); // 26

现在其他两个 RGB 值需要增加与 255 之间距离的相同分数。

为了解决这个问题,我们获得了最低 RGB 值(在增加它之前)与 255 之间的差值。红色为零,因此我们的差值为 255。然后我们得到最低 RGB 值在我们变换中增加的量。红色从零增加到 26,因此我们的增加值为 26。

将增加值除以原始颜色与 255 之间的差值,我们得到一个分数,我们可以用它来计算其他值。

const redDiff = 255 - red; // 255
const redIncrease = newRed - red; // 26
const increaseFraction = redIncrease / redDiff; // 0.10196

现在我们将其他 RGB 值与 255 之间的差值乘以这个分数。这给了我们需要添加到每个值中的量。

const newGreen = Math.round(153 + (255 - 153) * increaseFraction); // 163
const newBlue = Math.round(255 + (255 - 255) * increaseFraction); // 255

这意味着我们最终得到的颜色是 rgb(26, 163, 255)。这仍然是相同的色调,但稍微亮了一点。

这是一个执行此操作的函数:

function lightenByTenth(rgb) {

  const rgbIntArray = rgb.replace(/ /g, '').slice(4, -1).split(',').map(e => parseInt(e));
  // Grab the values in order of magnitude 
  // This uses the getLowestMiddleHighest function from the saturate section
  const [lowest,middle,highest]=getLowestMiddleHighest(rgbIntArray);
  
  if(lowest.val===255){
    return rgb;
  }
  
  const returnArray = [];

  // First work out increase on lower value
  returnArray[lowest.index]= Math.round(lowest.val+(Math.min(255-lowest.val,25.5)));

  // Then apply to the middle and higher values
  const increaseFraction  = (returnArray[lowest.index]-lowest.val)/ (255-lowest.val);
  returnArray[middle.index]= middle.val +(255-middle.val)*increaseFraction ;
  returnArray[highest.index]= highest.val +(255-highest.val)*increaseFraction ;
  
  // Convert the array back into an rgb string
  return (`rgb(${returnArray.join()})`);
}

如何保持色调不变地使 RGB 颜色变暗

使 RGB 颜色变暗非常类似。我们不是将值加到 255 来增加,而是从值中减去以接近零。

此外,我们通过减少最高值并获取这个减少值的比例来开始我们的变换。我们使用这个比例来减少其他两个值,减少它们到零的距离。这是我们使颜色变亮的逆过程。

使颜色变暗也会导致它逐渐失去饱和度。

function darkenByTenth(rgb) {
  
  // Our rgb to int array function again
  const rgbIntArray = rgb.replace(/ /g, '').slice(4, -1).split(',').map(e => parseInt(e));
  //grab the values in order of magnitude 
  //this uses the function from the saturate function
  const [lowest,middle,highest]=getLowestMiddleHighest(rgbIntArray);
  
  if(highest.val===0){
    return rgb;
  }

  const returnArray = [];

  returnArray[highest.index] = highest.val-(Math.min(highest.val,25.5));
  const decreaseFraction  =(highest.val-returnArray[highest.index])/ (highest.val);
  returnArray[middle.index]= middle.val -middle.val*decreaseFraction; 
  returnArray[lowest.index]= lowest.val -lowest.val*decreaseFraction;              
                            
  // Convert the array back into an rgb string
  return (`rgb(${returnArray.join()}) `);
}

这里有一个 CodePen,你可以用来尝试亮度函数的效果。


如果你需要处理 RGB 颜色,这些函数将帮助你入门。你也可以尝试使用 HSL 格式,以及颜色库来扩展浏览器支持,以及 Colour Grid 工具 进行转换。