Cómo manipular colores CSS con JavaScript

Share

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 rgbfunció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 Mes el máximo de los valores RGB y mes el mínimo:

La fórmula matemática para la ligereza.

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 Lrepresenta la luminosidad:

La fórmula matemática para la saturación.

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:

La fórmula matemática para el tono.

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.

La fórmula matemática para el croma.

También tenemos un valor de tono temporal, cuyo rango usaremos para decidir en qué “segmento” del círculo de tono pertenecemos:

La fórmula matemática para hue prime

A continuación, tenemos un valor “x”, que se utilizará como el valor del componente medio (el segundo más grande):

La fórmula matemática para un valor temporal “x”

Tenemos un valor “m”, que se utiliza para ajustar cada uno de los valores de luminosidad:

La fórmula matemática para la coincidencia de ligereza

Dependiendo del valor de primer tono, las rgblos valores se asignarán a CX0:

La fórmula matemática para valores RGB sin tener en cuenta la ligereza.

Por último, necesitamos mapear cada valor para ajustar la ligereza:

La fórmula matemática para explicar la ligereza con RGB

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 rotateHuefunción acepta un rotationpará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 saturatelightenun color, o, inversamente, 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)
});

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.

Pablo J.

Desarrollador gráfico y web, con ganas de trabajar y aprender todo lo posible de este campo tan variado. Trato de ser creativo en la vida laboral como personal. Amante de la buena lectura, el cine con sentido e inteligente.

You may also like...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *