Modelos en color 101
Sé que estás aquí para aprender sobre la manipulación de colores, y llegaremos allí. Pero antes de hacerlo, necesitamos una comprensión básica de cómo CSS destaca los colores. CSS utiliza dos modelos de color diferentes: RGB y HSL. Echemos un vistazo rápido a ambos.
RGB
Una inicialización para «rojo, verde, azul» RGB consiste en tres números que cada uno significa cuánta luz de su color respectivo se incluye en el color final resultante. En CSS, cada uno de estos números está en el rango de 0–255 y se escribiría como parámetros separados por comas de la rgb
función CSS . Por ejemplo, .rgb(50,100,0)
RGB es un sistema de color «aditivo», lo que significa que cuanto mayor sea cada número, más brillante será el color final. Si todos los valores son iguales, el color será en escala de grises; si todos los valores son cero, el resultado será negro; y si todos los valores son 255, el resultado será blanco.
Alternativamente, puede anotar colores RGB usando la notación hexadecimal, en la cual el número entero de cada color se convierte de la base 10 a la base 16. Por ejemplo, sería .rgb(50,100,0)
#326400
Aunque generalmente me encuentro buscando RGB (particularmente hexadecimal) por costumbre, a menudo encuentro que es difícil de leer y especialmente difícil de manipular. Ingrese HSL.
HSL
Una inicialización para «matiz, saturación, luz», HSL también consta de tres valores. El valor de tono corresponde a la posición en la rueda de color y está representado por un valor de ángulo CSS; más comúnmente, se utilizan unidades deg.
La saturación, representada por un porcentaje, se refiere a la intensidad del color. Cuando la saturación es del 100 por ciento, está completamente coloreada; cuanto menos saturación, menos color, hasta que alcanza la escala de grises al 0 por ciento.
La claridad, también representada por un porcentaje, se refiere a cuán brillante es un color. El brillo «regular» es del 50 por ciento. Una claridad del 100 por ciento será de color blanco puro, y una claridad del 0 por ciento será de color negro puro, independientemente de los valores de matiz y saturación.
Me parece que HSL es un modelo más intuitivo. Las relaciones entre los colores son más evidentes de inmediato, y la manipulación de los colores tiende a ser tan simple como ajustar solo uno de los números.
Conversión entre modelos de color.
Los modelos de color RGB y HSL desglosan un color en varios atributos. Para convertir entre las sintaxis, primero debemos calcular estos atributos.
Con la excepción del tono, cada valor que hemos discutido se puede representar como un porcentaje. Incluso los valores RGB son representaciones de porcentajes de tamaño de byte. En las fórmulas y funciones a continuación, estos porcentajes estarán representados por decimales entre 0 y 1.
Me gustaría señalar que no cubriré las matemáticas en profundidad; más bien, repasaré brevemente la fórmula matemática original y luego la convertiré en una fórmula de JavaScript.
Cálculo de ligereza a partir de RGB
La ligereza es el más fácil de los tres valores HSL para calcular. Matemáticamente, la fórmula se muestra de la siguiente manera, donde M
es el máximo de los valores RGB y m
es el mínimo:
Aquí está la misma fórmula que una función de JavaScript:
const rgbToLightness = (r,g,b) =>
1/2 * (Math.max(r,g,b) + Math.min(r,g,b));
Cálculo de saturación a partir de RGB
La saturación es solo un poco más complicada que la ligereza. Si la luminosidad es 0 o 1, entonces el valor de saturación será 0. De lo contrario, sigue la siguiente fórmula matemática, donde L
representa la luminosidad:
Como 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));
};
Cálculo del tono a partir de RGB
La fórmula para calcular el ángulo de matiz a partir de coordenadas RGB es un poco más compleja:
Como JavaScript:
const rgbToHue = (r,g,b) => Math.round(
Math.atan2(
Math.sqrt(3) * (g - b),
2 * r - g - b,
) * 180 / Math.PI
);
La multiplicación de al final es convertir el resultado de radianes a grados.180 / Math.PI
Cálculo de HSL
Todas estas funciones pueden integrarse en una única función de utilidad:
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];
}
Cálculo de RGB a partir de HSL
Antes de saltar al cálculo de RGB, necesitamos algunos valores de requisitos previos.
También tenemos un valor de tono temporal, cuyo rango usaremos para decidir en qué «segmento» del círculo de tono pertenecemos:
A continuación, tenemos un valor «x», que se utilizará como el valor del componente medio (el segundo más grande):
Tenemos un valor «m», que se utiliza para ajustar cada uno de los valores de luminosidad:
Dependiendo del valor de primer tono, las r
, g
y b
los valores se asignarán a C
, X
y 0
:
Por último, necesitamos mapear cada valor para ajustar la ligereza:
Poniendo todo esto junto en una función de 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); } }
Crear un objeto de color
Para facilitar el acceso al manipular sus atributos, trataremos con un objeto JavaScript. Esto se puede crear envolviendo las funciones escritas previamente:
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}; }
Ejemplo
Te recomiendo que pases un tiempo jugando con este ejemplo . Ver cómo interactúa cada uno de los atributos cuando ajusta los demás puede brindarle una comprensión más profunda de cómo se combinan los dos modelos de color.
Manipulación del color
Ahora que tenemos la capacidad de convertir entre modelos de color, ¡veamos cómo podemos manipular estos colores!
Actualizar atributos
Cada uno de los atributos de color que hemos cubierto puede manipularse individualmente, devolviendo un nuevo objeto de color. Por ejemplo, podemos escribir una función que rote el ángulo de tono:
const rotateHue = rotation => ({hue, ...rest}) => { const modulo = (x, n) => (x % n + n) % n; const newHue = modulo(hue + rotation, 360);
return { ...rest, hue: newHue }; }
La rotateHue
función acepta un rotation
parámetro y devuelve una nueva función, que acepta y devuelve un objeto de color. Esto permite la creación fácil de nuevas funciones de «rotación»:
const rotate30 = rotateHue(30); const getComplementary = rotateHue(180);
const getTriadic = color => { const first = rotateHue(120); const second = rotateHue(-120); return [first(color), second(color)]; }
En la misma línea, puede escribir funciones saturate
o lighten
un color, o, inversamente, desaturate
o darken
.
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) });
Predicados de color
Además de la manipulación del color, puede escribir «predicados», es decir, funciones que devuelven un valor booleano.
const isGrayscale = ({saturation}) => saturation === 0;
const isDark = ({lightness}) => lightness < .5;
Manejo de matrices de colores
Filtros
El método JavaScript acepta un predicado y devuelve una nueva matriz con todos los elementos que «pasan». Los predicados que escribimos en la sección anterior se pueden usar aquí:[].filter
const colors = [/* ... an array of color objects ... */];
const isLight = ({lightness}) => lightness > .5;
const lightColors = colors.filter(isLight);
Clasificación
Para ordenar un conjunto de colores, primero debe escribir una función de «comparador». Esta función toma dos elementos de una matriz y devuelve un número para denotar al «ganador». Un número positivo indica que el primer elemento debe clasificarse primero, y un negativo indica que el segundo debe clasificarse primero. Un valor cero indica un empate.
Por ejemplo, aquí hay una función para comparar la claridad de dos colores:
const compareLightness = (a,b) => a.lightness - b.lightness;
Aquí hay una función que compara la saturación:
const compareSaturation = (a,b) => a.saturation - b.saturation;
En un esfuerzo por evitar la duplicación en nuestro código, podemos escribir una función de orden superior para devolver una función de comparación para comparar cualquier atributo:
const compareAttribute = attribute => (a,b) => a[attribute] - b[attribute];
const compareLightness = compareAttribute('lightness'); const compareSaturation = compareAttribute('saturation'); const compareHue = compareAttribute('hue');
Atributos promedio
Puede promediar los atributos específicos de una matriz de colores componiendo varios métodos de matriz de JavaScript. Primero, puede calcular el promedio de un atributo sumando con reduce y dividiendo por la longitud de la matriz:
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;
Puede usar esto para «normalizar» una variedad de colores:
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');
Conclusión
Los colores son una parte integral de la web. Desglosar los colores en sus atributos permite la manipulación inteligente de los colores y abre la puerta a todo tipo de posibilidades.