Программное осветление или затемнение HEX-цвета (или RGB) и смешивание цветов
Я работаю над функцией для программного осветления или затемнения цвета в формате HEX на определённое значение. Достаточно передать строку, например, "3F6D2A"
для цвета (col
) и целое число в десятичной системе (amt
) для величины осветления или затемнения. Чтобы затемнить цвет, нужно передать отрицательное число (например, -20
).
Причина, по которой я решил это реализовать, заключается в том, что все найденные решения казались излишне сложными, и у меня было ощущение, что это можно сделать всего в несколько строк кода. Буду признателен за любые замечания или предложения по улучшению производительности.
Вот код функции:
function LightenDarkenColor(col, amt) {
col = parseInt(col, 16);
return (((col & 0x0000FF) + amt) | ((((col >> 8) & 0x00FF) + amt) << 8) | (((col >> 16) + amt) << 16))).toString(16);
}
// ТЕСТ
console.log(LightenDarkenColor("3F6D2A", 40));
Для разработки я подготовил более читаемую версию кода:
function LightenDarkenColor(col, amt) {
var num = parseInt(col, 16);
var r = (num >> 16) + amt;
var b = ((num >> 8) & 0x00FF) + amt;
var g = (num & 0x0000FF) + amt;
var newColor = g | (b << 8) | (r << 16);
return newColor.toString(16);
}
// ТЕСТ
console.log(LightenDarkenColor("3F6D2A", -40));
И вот версия, которая обрабатывает цветовые значения с или без символа #
в начале, а также проверяет на корректность значений:
function LightenDarkenColor(col, amt) {
var usePound = false;
if (col[0] === "#") {
col = col.slice(1);
usePound = true;
}
var num = parseInt(col, 16);
var r = (num >> 16) + amt;
// Ограничение значений цветовых каналов
r = Math.min(Math.max(r, 0), 255);
var b = ((num >> 8) & 0x00FF) + amt;
b = Math.min(Math.max(b, 0), 255);
var g = (num & 0x0000FF) + amt;
g = Math.min(Math.max(g, 0), 255);
return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16);
}
Я понимаю, что функция стала чуть более сложной, но при этом она выглядит более понятной и используется довольно просто, если не учитывать #
и проверки на корректность цветовых значений.
Вопрос заключается в следующем: прав ли я в своих предположениях? Охватывает ли это большинство (обычных) ситуаций? И если да, то какой самый быстрый и минимистичный способ решения этой задачи? Я планирую использовать это в анимациях и в небольшом окружении, поэтому скорость является самым важным фактором, размер - на втором месте, точность - на третьем, а читаемость... не входит в список требований (извините, знаю, что некоторые из вас сейчас вырывают волосы!).
5 ответ(ов)
Если вы ищете способ изменения оттенков цвета в JavaScript, вот решение, которое отлично работает для меня:
function shadeColor(color, percent) {
var R = parseInt(color.substring(1, 3), 16);
var G = parseInt(color.substring(3, 5), 16);
var B = parseInt(color.substring(5, 7), 16);
R = parseInt(R * (100 + percent) / 100);
G = parseInt(G * (100 + percent) / 100);
B = parseInt(B * (100 + percent) / 100);
R = (R < 255) ? R : 255;
G = (G < 255) ? G : 255;
B = (B < 255) ? B : 255;
R = Math.round(R);
G = Math.round(G);
B = Math.round(B);
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
return "#" + RR + GG + BB;
}
Примеры использования:
Чтобы осветлить цвет, вы можете вызвать функцию следующим образом:
shadeColor("#63C6FF", 40);
А чтобы потемнить цвет, просто передайте отрицательное значение:
shadeColor("#63C6FF", -40);
Этот код позволяет легко изменять яркость заданного цвета, добавляя или убирая заданный процент. Вы можете использовать его для создания динамических интерфейсов или адаптивных цветов в вашем приложении.
Вот простой однострочник на основе ответа Эрика:
function adjust(color, amount) {
return '#' + color.replace(/^#/, '').replace(/../g, color => ('0'+Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}
Примеры использования:
adjust('#ffffff', -20) => "#ebebeb"
adjust('000000', 20) => "#141414"
Эта функция adjust
принимает цвет в формате HEX и число amount
, на которое нужно изменить яркость цвета. Она удаляет символ #
, разбивает цвет на составляющие компоненты, корректирует каждую компоненту, добавляя amount
, и затем собирает их обратно в строку формата HEX.
Вот мой вариант функции, основанный на вашей реализации. Я предпочитаю использовать шаги вместо процентов, так как для меня это более интуитивно понятно.
Например, 20% от 200 синих значений значительно отличается от 20% от 40 синих значений.
В любом случае, вот моя модификация. Спасибо за исходный код!
function adjustBrightness(col, amt) {
var usePound = false;
if (col[0] == "#") {
col = col.slice(1);
usePound = true;
}
var R = parseInt(col.substring(0, 2), 16);
var G = parseInt(col.substring(2, 4), 16);
var B = parseInt(col.substring(4, 6), 16);
// чтобы сделать цвет менее ярким, чем исходный
// измените следующие три символа "+" на "-"
R = R + amt;
G = G + amt;
B = B + amt;
if (R > 255) R = 255;
else if (R < 0) R = 0;
if (G > 255) G = 255;
else if (G < 0) G = 0;
if (B > 255) B = 255;
else if (B < 0) B = 0;
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
return (usePound ? "#" : "") + RR + GG + BB;
}
Если у вас возникнут вопросы по функционалу или если нужны доработки, не стесняйтесь спрашивать!
Ответ на этот вопрос основан на ответах Давида Шеррета и Пабло. Мы переписали решение в более безопасном формате для TypeScript:
/**
* @param color Hex value format: #ffffff или ffffff
* @param decimal Значение для осветления или затемнения, например, 0.5 для осветления на 50% или 1.5 для затемнения на 50%.
* @returns Измененный цвет в формате hex.
*/
static shadeColor(color: string, decimal: number): string {
// Проверяем, начинается ли цвет с '#', и устанавливаем base в соответствующее значение
const base = color.startsWith('#') ? 1 : 0;
// Извлекаем красный, зеленый и синий компоненты цвета из hex
let r = parseInt(color.substring(base, 2 + base), 16);
let g = parseInt(color.substring(2 + base, 4 + base), 16);
let b = parseInt(color.substring(4 + base, 6 + base), 16);
// Применяем осветление или затемнение к каждому компоненту
r = Math.round(r / decimal);
g = Math.round(g / decimal);
b = Math.round(b / decimal);
// Ограничиваем значения каждого компонента не выше 255
r = Math.min(r, 255);
g = Math.min(g, 255);
b = Math.min(b, 255);
// Приводим компоненты к строковому виду, добавляя ведущий ноль при необходимости
const rr = r.toString(16).padStart(2, '0');
const gg = g.toString(16).padStart(2, '0');
const bb = b.toString(16).padStart(2, '0');
// Возвращаем цвет в формате hex
return `#${rr}${gg}${bb}`;
}
В этом обновленном коде учтены несколько важных особенностей:
- Использование
Math.min()
вместо тернарного оператора для ограничения значений компонентов цвета. - Применение метода
padStart(2, '0')
для правильного форматирования значений RGB в hex-строку, что делает код более простым и читаемым.
Теперь эта функция безопаснее для использования в TypeScript и лучше обрабатывает входные данные.
Похоже, что в вашей функции есть небольшая ошибка: если финальное значение 'r' состоит из одной цифры, результат будет выглядеть как 'a0a0a', тогда как правильный результат должен быть '0a0a0a', например.
Я быстро исправил это, добавив следующий код вместо вашего возвращаемого значения:
var rStr = (r.toString(16).length < 2) ? '0' + r.toString(16) : r.toString(16);
var gStr = (g.toString(16).length < 2) ? '0' + g.toString(16) : g.toString(16);
var bStr = (b.toString(16).length < 2) ? '0' + b.toString(16) : b.toString(16);
return (usePound ? "#" : "") + rStr + gStr + bStr;
Возможно, это не самый элегантный способ, но он работает. Кстати, отличная функция! Именно то, что мне было нужно. 😃
Где найти документацию по форматированию даты в JavaScript?
В чем разница между String.slice и String.substring?
Проверка соответствия строки регулярному выражению в JS
Существует ли ссылка на "последнюю" библиотеку jQuery в Google APIs?
Как создать диалог с кнопками "Ок" и "Отмена"