如何使用 JavaScript 控制 CSS 中的颜色

作者:Adam Giese

2019 年 5 月 24 日

如何使用 JavaScript 控制 CSS 中的颜色

背景知识:颜色模型


点开这篇文章的你,肯定是想要学习怎样控制颜色的——我们后面就会讲具体操作。但首先,我们需要对 CSS 如何标记颜色有一个基本的认识。CSS 使用的是两种颜色模型:RGB 和 HSL,我们先简单了解一下。


RGB


RGB 就是“红色,绿色,蓝色”的简称。这个模型由三个数字组成,每个数字表示其所代表的颜色在最终生成的颜色中有多高的亮度。在 CSS 中,每个数值的范围都是 0-255,三个数值间用逗号分隔,作为 CSS rgb 函数的参数,例如:rgb(50,100,0)。


RGB 是一种“增量”颜色系统。这意味着每个数字越高,最终生成的颜色就越亮。如果所有值都相等就生成灰度颜色;如果所有值都为零,结果为黑色;如果所有值都是 255,则结果为白色。


此外你也可以使用十六进制表示法来标记 RGB 颜色,其中每种颜色的数值从 10 进制转换为 16 进制。例如,rgb(50,100,0)用 16 进制就写成#326400。


虽然我个人比较习惯使用 RGB 模型(特别是十六进制),但我也经常发现它不易阅读,也不容易操作。下面来看 HSL 模型。


HSL


HSL 是“色调,饱和度,光线”的简称,HSL 也包含三个值。色调值对应于色轮上的点,由 CSS 角度值表示,最常用的是度数单位。


饱和度以百分比表示,是指颜色的强度。当饱和度为 100%时颜色最深,饱和度越低,颜色越浅,直到灰度为 0%。


亮度也以百分比表示,指的是颜色有多亮。 “常规”的亮度是 50%。无论色调和饱和度值如何,100%的亮度都是纯白色,0%的亮度就是纯黑色。


我觉得 HSL 模型更直观一些,颜色之间的关系更加明显,控制颜色时只要简单地调整几个数字就可以了。


颜色模型之间的转换


RGB 和 HSL 颜色模型都将颜色分解为各种属性。要在不同模型之间进行转换,我们首先需要计算这些属性。


除了色调,上面提到的所有数值都可以表示为百分比。就连 RGB 值也是用字节表示的百分比。 在下面提到的公式和函数中,这些百分比将由 0 到 1 之间的小数来表示。


这里提一下,我并不会深入探讨这些数学知识;相比之下,我将简要介绍一遍原始数学公式,然后将其转换为 JavaScript 格式。


从 RGB 模型中计算亮度


亮度是三个 HSL 值中最容易计算的一个。其数学式如下,其中 M 是 RGB 值的最大值,m 是最小值:



亮度的数学式


用 JavaScript 函数写成下面的形式:


const rgbToLightness =(r,g,b)=>     1/2 *(Math.max(r,g,b)+ Math.min(r,g,b));
复制代码


从 RGB 模型中计算饱和度


饱和度仅比亮度稍微复杂一些。 如果亮度为 0 或 1,则饱和度值为 0;否则,它基于下面的数学公式计算得出,其中 L 表示亮度:



饱和度的数学式


写成 JavaScript:


const rgbToSaturation = (r,g,b) => {  const L = rgbToLightness(r,g,b);  const max = Math.max(r,g,b);  const min = Math.min(r,g,b);  return (L === 0 || L === 1)   ? 0   : (max - min)/(1 - Math.abs(2 * L - 1));};
复制代码


从 RGB 模型中计算色调


从 RGB 坐标中计算色调角度的公式有点复杂:



色调的数学式


写成 JavaScript:


const rgbToHue = (r,g,b) => Math.round(  Math.atan2(    Math.sqrt(3) * (g - b),    2 * r - g - b,  ) * 180 / Math.PI);
复制代码


最后 180 / Math.PI 的算法是将结果从弧度转换为度。


计算 HSL


上面这些函数都可以包含在同一个功能函数里:


const rgbToHsl = (r,g,b) => {  const lightness = rgbToLightness(r,g,b);  const saturation = rgbToSaturation(r,g,b);  const hue = rgbToHue(r,g,b);  return [hue, saturation, lightness];}
复制代码


从 HSL 模型中计算 RGB 值


开始计算 RGB 之前,我们需要一些前提值。


首先是“色度”值:



色度的数学式


还有一个临时的色调值,我们将用它来确定我们所属的色调圈的“段”:



色调区间的数学式


接下来,我们设一个“x”值,它将用作中间(第二大)组件值:



临时“x”值的数学式


我们再设一个“m”值,用于调整各个亮度值:



亮度匹配的数学式


根据色调区间值,r,g 和 b 值将映射到 C,X 和 0:



RGB 值的数学式,不考虑亮度


最后,我们需要映射每个值以调整亮度:



用 RGB 来解释亮度的数学式


将上面这些都写到 JavaScript 函数中:


const hslToRgb = (h,s,l) => {  const C = (1 - Math.abs(2 * l - 1)) * s;  const hPrime = h / 60;  const X = C * (1 - Math.abs(hPrime % 2 - 1));  const m = l - C/2;  const withLight = (r,g,b) => [r+m, g+m, b+m];if (hPrime <= 1) { return withLight(C,X,0); } else  if (hPrime <= 2) { return withLight(X,C,0); } else  if (hPrime <= 3) { return withLight(0,C,X); } else  if (hPrime <= 4) { return withLight(0,X,C); } else  if (hPrime <= 5) { return withLight(X,0,C); } else  if (hPrime <= 6) { return withLight(C,0,X); }}
复制代码


创建颜色对象


为了便于在操作属性时访问,我们将创建一个 JavaScript 对象。把前面提到的这些函数打包起来就能创建这个对象:


const rgbToObject = (red,green,blue) => {  const [hue, saturation, lightness] = rgbToHsl(red, green, blue);  return {red, green, blue, hue, saturation, lightness};}
const hslToObject = (hue, saturation, lightness) => { const [red, green, blue] = hslToRgb(hue, saturation, lightness); return {red, green, blue, hue, saturation, lightness};}
复制代码


示例


我强烈建议你花些时间看看这个示例(https://codepen.io/AdamGiese/full/86b353c35a8bfe0868a8b48683faf668),从中了解调节各个属性时其它属性如何发生变化,这样能帮助你更深入地了解两种颜色模型是如何对应的。


颜色控制


现在我们已经知道怎样在颜色模型之间进行转换了,那么就来看看该如何控制这些颜色!


更新属性


我们提到的所有颜色属性都可以单独控制,返回一个新的颜色对象。例如,我们可以编写一个旋转色调角度的函数:


const rotateHue = rotation => ({hue, ...rest}) => {  const modulo = (x, n) => (x % n + n) % n;  const newHue = modulo(hue + rotation, 360);return { ...rest, hue: newHue };}
复制代码


rotateHue 函数会接受一个旋转参数并返回一个新函数,该函数接受并返回一个颜色对象。这样就可以轻松创建新的“旋转”函数:


const rotate30 = rotateHue(30);const getComplementary = rotateHue(180);const getTriadic = color => {  const first = rotateHue(120);  const second = rotateHue(-120);  return [first(color), second(color)];}
复制代码


用这种方式,你也可以编写加深或提亮颜色的函数——或者反过来,减淡或变暗也行。


const saturate = x => ({saturation, ...rest}) => ({  ...rest,  saturation: Math.min(1, saturation + x),});const desaturate = x => ({saturation, ...rest}) => ({  ...rest,  saturation: Math.max(0, saturation - x),});const lighten = x => ({lightness, ...rest}) => ({  ...rest,  lightness: Math.min(1, lightness + x)});const darken = x => ({lightness, ...rest}) => ({  ...rest,  lightness: Math.max(0, lightness - x)});
复制代码


颜色谓词


除了颜色控制以外,你还可以编写“谓词”——亦即返回布尔值的函数。


const isGrayscale = ({saturation}) => saturation === 0;const isDark = ({lightness}) => lightness < .5;
复制代码


处理颜色数组


过滤器


JavaScript [] .filter 方法会接受一个谓词并返回一个新数组,其中包含所有“传递”的元素。我们在上一节中编写的谓词可以用在这里:


const colors = [/* ... an array of color objects ... */];const isLight = ({lightness}) => lightness > .5;const lightColors = colors.filter(isLight);
复制代码


排序


要对颜色数组进行排序,首先需要编写一个“比较器”函数。 此函数接受一个数组的两个元素并返回一个数字来表示“赢家”。正数表示第一个元素应该先排序,而负数表示第二个元素应该先排序。 零值表示平局。


例如,这是一个比较两种颜色亮度的函数:


const compareLightness = (a,b) => a.lightness - b.lightness;
复制代码


这是一个比较饱和度的函数:


const compareSaturation = (a,b) => a.saturation - b.saturation;
复制代码


为了防止代码重复,我们可以编写一个高阶函数来返回一个比较函数来对比各种属性:


const compareAttribute = attribute =>  (a,b) => a[attribute] - b[attribute];const compareLightness = compareAttribute('lightness');const compareSaturation = compareAttribute('saturation');const compareHue = compareAttribute('hue');
复制代码


平均属性


你可以搭配各种 JavaScript 数组方法来平衡颜色数组中的特定属性。首先,你可以使用 reduce 求和并用 Array length 属性分割来计算一个属性的均值:


const colors = [/* ... an array of color objects ... */];const toSum = (a,b) => a + b;const toAttribute = attribute => element => element[attribute];const averageOfAttribute = attribute => array =>  array.map(toAttribute(attribute)).reduce(toSum) / array.length;
复制代码


你可以用它来“规范化”一组颜色:


/* ... continuing */const normalizeAttribute = attribute => array => {  const averageValue = averageOfAttribute(attribute)(array);  const normalize = overwriteAttribute(attribute)(averageValue);  return normalize(array);}
const normalizeSaturation = normalizeAttribute('saturation');const normalizeLightness = normalizeAttribute('lightness');const normalizeHue = normalizeAttribute('hue');
复制代码


结论


颜色是网络不可或缺的一部分。将颜色分解为属性就可以灵活控制它们,并创造出无限的可能。


插件:LogRocket,一个用于网络应用的 DVR



https://logrocket.com/signup/](https://logrocket.com/signup/


LogRocket 是一个前端日志记录工具,可让你像在自己的浏览器中一样重放问题。LogRocket 不会去猜测错误的成因,也不需要用户提供屏幕截图和日志转储,而是让你重播会话以快速了解问题的根源。它适用于任何应用程序,不受框架限制,并且有从 Redux,Vuex 和 @ngrx / store 记录其他上下文的插件。


除了记录 Redux 操作和状态之外,LogRocket 还记录控制台日志、JavaScript 错误、堆栈跟踪、带有标头+主体的网络请求/响应、浏览器元数据和自定义日志。它还使用 DOM 来记录页面上的 HTML 和 CSS,就算是最复杂的单页应用也能用它录下像素级的完美视频记录。


免费试用点这里(https://logrocket.com/signup/)。


查看英文原文https://blog.logrocket.com/how-to-manipulate-css-colors-with-javascript-fb547113a1b8


2019 年 5 月 24 日 08:005282

评论

发布
暂无评论
发现更多内容

缓存一致性协议的工作方式

HackMSF

缓存 并发

做正确的事情什么时候都不晚

Neco.W

学习 导师

中小型城市商业银行数字化转型实践(四)业务中台建设思路和路径

泡菜小仙

行业资讯 银行数字化转型

述职报告的一些建议

黄大路

职场

食堂就餐卡系统设计

上山砍柴

极客大学架构师训练营

人生元编程

欧雷

人生 程序人生 个人感悟

体验碎片-06/06

黄大路

产品经理

近邻算法介绍

黄大路

数据挖掘 数据分析

游戏夜读 | 终端设备之争?

game1night

愿你也能穿越熊熊烈火,飞往你的山

Janenesome

读书笔记 思考

练习--食堂就餐卡系统技术方案设计

jason

新媒体小编一年工作心得

彭宏豪95

职场 感悟 工作

我是这样给同事分析幂等性问题的

flyer0126

幂等性 服务设计

产品周刊 | 第 18 期(20200607)

Herbert

产品 产品经理 产品设计 产品推荐

Backend Roadmap

陈皮

Backend Developer

现在的我们想要获得成功,除了付出努力之外,还要具备见识。

叶小鍵

内存屏障的来历

HackMSF

cpu 并发

Spark Launcher Java API提交Spark算法

杨仪军

spark spark launcher

话说 Java vs C#

阿喜伯

食堂就餐卡系统设计

当代社畜在维权中成长 | 记初次打官司

张鸱鸺

个人成长 随笔杂谈 维权 民事诉讼

「架构师训练营」第1周作业 - 食堂就餐卡系统设计

guoguo 👻

极客大学架构师训练营

test

PQY

【摘】Git-从零单排 03期

卡尔

git 效率工具 工具链

B端产品经理养成记(3):访谈

涛哥

产品经理

线上故障处理实践

心平气和

故障分析 故障定位

「翻译」测试用例最佳实践

HackMSF

翻译 单元测试

游戏夜读 | 核心能力是什么?

game1night

初识 Docker 与安装 | Docker 系列

AlwaysBeta

Docker

UML统一建模语言

哼哼

对文化的态度,早在日常中

zhoo299

历史 随笔 文化

如何使用 JavaScript 控制 CSS 中的颜色-InfoQ