如何使用JavaScript操作CSS颜色

发布于 大漠

在学习如何使用JavaScript操作CSS颜色之前,我们需要对CSS如何设置颜色有一个基本的了解。CSS设置颜色模式有多种,最为常见的模型有:RGBHSL。我们先来看一下这两种颜色模式。

颜色模式

RGB

RGBredgreenblue三个单词首字母的缩写,其由三个数字组成,每个数字表示其各自颜色的光在最终颜色中包含多少。在CSS中,这些数字都在0~255之间,可以作为CSS的rgb函数的参数,并且用逗号来分隔。例如rgb(50,100,0)

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

另外,你也可以使用十六进制表示RGB颜色,在这种表示法中,每种颜色的整数都是从基数10转换为基数16。例如,rgb(50,100,0)转换出来的十六进制就是#326400

虽然我们经常发现自己出于习惯而使用RGB(特别是十六进制),但我经常发现它很难读,而且特别难操作。

HSL

HSL是huesaturationlight三个单词首字母的简写,也是由这三个值组成。色调对应于色盘上的位置,由CSS的角度值表示,最常见的是deg单位。

饱和度用百分比表示,是指颜色的强度。当饱和度为100%时,它是一个彩色;当饱和度越底,颜色就越少,直到0%,就会是一个灰色。

亮度也是用百分比来表示,指的是颜色亮度。常规亮度是50%。无论色调和饱和度如何,100%的亮度将是纯白色,而0%的亮度将是纯黑色。

HSL颜色模型是一个更直观的颜色模型。颜色之间的关系更加明显,而对颜色的操作往往就像调整一个数字一样简单。

有关于CSS颜色更深入的介绍,可以阅读下面相关文章进行扩展:

颜色模型之间的转换

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

除了色调,我们讨论过的每个值都可以用百分比表示。甚至RGB值也是字节大小的百分比表示。在下面的公式和函数中,这些百分比将用01之间的小数表示。

我想指出的是,我们在这篇文章中不会深入讨论这些数学,相反,我将简要介绍原始的数学公式,然后将其转换为JavaScript公式。

从RGB中计算亮度

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

JavaScript表达即如下:

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

从RGB中计算饱和度

饱和度只比亮度稍微复杂一点。如果亮度为01,则饱和度值为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之前,我们需要一些先决值。首先是色度(chroma)值:

还需要一个临时的色相值,将使用它的范围来决定我们属于色盘中的哪个阶段:

接下来,我们有一个x值,它将用作中间值:

还需要一个m值,用来调整每个亮度值:

根据色调素数,rgb的值将映射到CX0

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

对应的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}
}

示例

比如下面这个示例,当你调整其他属性时,查看每个属性如何交互,这样可以让你更深入地了解两个颜色模型之间是如何相互转换的。

颜色处理

通过上面的内容我们了解和掌握了颜色模型之间转换。接下来看看JavaScript如何操纵这些颜色。

更新属性

我们已经讨论的每个颜色属性都可以单独操作,返回一个新的color对象。例如,我们可以写一个色相角度旋转的函数:

const rotateHue = rotation => ({hue, ...rest}) => {
    const module (x, n) => (x % n + n) % n
    const newHue = module(hue + rotation, 360)
    return {...rest, hue:newHue}
}

rotateHue函数接受一个rotation参数并返回一个新函数,该函数接受并返回一个color对象。这使得创建新的旋转函数变得容易:

const rotate30 = rotateHue(30)
const getComplementary = rotateHue(180)

const getTriadic = color => {
    const first = rotateHue(120)
    const second = rotateHue(-120)
    return [first(color), second(color)]
}

沿着同样的思路,我们可以编写一个颜色的saturatelighten函数,或者desaturatedarken

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;

颜色数组的处理

颜色过滤

[].filter方法接受一个布尔值,并返回一个符合要求的新数组。

const colors = [/* 颜色对象的数组*/]
const isLight = ({lightness}) => lightness > .5
const lightColors = colors.filter(isLigght)

颜色排序

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

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

const compareLightness = (a, b) => a.lightness - b.lightness

下面是比较两个元素的饱和度:

const compareSaturation = (a, b) => a.saturaaation - 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和除以数组长度来计算一个属性的平均值:

const colors = [/* 颜色对象数组 */];
const toSum = (a,b) => a + b;
const toAttribute = attribute => element => element[attribute];
const averageOfAttribute = attribute => array => array.map(toAttribute(attribute)).reduce(toSum) / array.length;

你可以用这个来“正常化”一组颜色:

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');

生成随机色

很多时候我们想要的颜色是明确的,但有的时候我们需要一个随机的颜色。如果你的要求不高,使用JavaScript生成一个随机的颜色是非常的简单。比如我们希望得到一个随机的十六进制颜色。大家都知道一个十六进制颜色色是由六个字符组成,每个字符都是0f之间的任意值。我们可以写两个函数:

let getRandomColor = () => {
    let characters = '0123456789ABCDDEF'
    let color = '#'

    for (let i = 0; i < 6; i++) {
        color +=characters[getRandomNumber(0, 15)]
    }
    return color
}

let getRandomNumber = (min, max) => {
    let r = Math.floor(Math.random() * (max - min + 1)) + min
    return r
}

前面我们也提到过了,HSLRGB相关的颜色模式,事实上,我们平时使用HSLA或者RGBA模式较多,因为它们更易于我们控制一个颜色。不管是HSLA还是RGBA,每个颜色值都有一个范围。我们同样可以使用JavaScript来获取你设置范围内的随机颜色。比如下面这个HSLA示例。

let getRandomColor = (h, s, l, a) => {
    let hue = getRandomNumber(h[0], h[1])
    let saturation = getRandomNumber(s[0], s[1])
    let lightness = getRandomNumber(l[0], l[1])
    let alpha = getRandomNumber(a[0] * 100, a[1] * 100) / 100

    return {
        h: hue,
        s: saturation,
        l: lightness,
        a: alpha,
        hslaValue: getHSLAColor(hue, saturation, lightness, alpha)
    }
}

let getRandomNumber = (min, max) => {
    let r = Math.floor(Math.random() * (max - min + 1)) + min
    return r
}

let getHSLAColor = (h, s, l, a) => {
    return `hsla(${h}, ${s}%, ${l}%, ${a})`
}

使用也简单:

let h_range = [0, 360];
let s_range = [90, 100];
let l_range = [0, 90];
let a_range = [1, 1];

let color = getRandomColor(h_range, s_range, l_range, a_range);
document.body.style.backgroundColor = color.hslaValue;

比如下面这样的一个小示例:

有关于RGBA或者HSLRGB相关颜色模型的随机色也可以使用类似的方式生成,感兴趣的朋友不仿一试。

扩展阅读

特别声明,文章中的数学公式来自于@Adam GieseHow to manipulate CSS colors with JavaScript一文。

小结

Web颜色是Web应用程序或页面中不可或缺的一部分,对于CSS处理颜色总是非常的简单和单一。但很多时候我们需要一些特殊的效果,比如随机色等。那么我们就需要借助JavaScript来完成。而JavaScript处理Web颜色的情景也非常的多。正如上面介绍的,颜色模式之间的转换,颜色过滤和排序等。那么这篇文章整理了一些有关于JavaScript操作Web颜色的技巧。希望这些对初学者有所帮助,如果您在这方面有更多的经验,欢迎在下面的评论中与我们一起共享。Air Jordan XII 12 Shoes