Работа с цветами в Figma: яркость, контраст WCAG и другое

Создание плагина Figma: часть 4 из 6



** Код этого раздела находится здесь. **

Теперь у нас есть контур нашего плагина, который нам нужно заполнить пробелами.

🚦Получение «ненормального» значения RGB

Прежде чем мы действительно сможем взломать, нам нужно немного поработать. Вы, наверное, заметили, что Figma хранит цвета в таких объектах:

{r: 0.8999999761581421, g: 0.1875, b: 0.1875}

Значения каналов были нормализованы и находятся в диапазоне от 0 до 1. Чтобы избежать некоторых головных болей, мы предпочли бы работать с каналами в более общем диапазоне 0–255. Для этого мы можем написать функцию, которая преобразует их обратно:

Мы деструктурируем объект на r, g и b и помещаем его в массив. Затем мы перебираем массив, умножаем каждое значение на 255 и округляем до ближайшего целого числа. Мы делаем это, чтобы предотвратить появление странных десятичных знаков в наших значениях RGB, например: rgb(255, 171.5, 255).

Теперь мы можем сохранять эти «нормализованные» значения в некоторых глобальных переменных в верхней части code.js при изменении выбора. Таким образом, у нас всегда есть текущие выбранные цвета переднего плана и фона, и мы можем передавать их в качестве аргументов, когда это необходимо:

Хорошо, теперь мы можем перейти к самому сложному.

🧮 Расчет яркости

Чтобы наша функция контраста действительно работала, нам нужно написать функцию, которая получает относительную яркость цвета. Это, наверное, самая сложная часть процесса, но по сути это просто формула, которую вы можете найти здесь.

Вы можете полностью пропустить это небольшое объяснение, но, говоря простыми словами, оно работает следующим образом:

  • Мы разделяем наш цвет на каналы RGB, просматривая массив и работая над одним каналом за раз.
  • Нормализовать эти каналы между 0 и 1. Подождите, разве мы не написали что-то, чтобы отменить это? Ну да. По сложным причинам, связанным с округлением, нам все равно придется это сделать. 🤫
  • Убедитесь, что каждое нормализованное значение ниже или выше 0,03928. Это число как-то связано с порогом гаммы цветового пространства sRGB (что бы это ни значило). Если число меньше, мы делим значение на 12,92. Если он выше, мы прибавляем 0,55, делим на 1,055 и затем умножаем результат на степень 2,4.
  • После этого каждый канал умножается на определенное значение. Red * 0.2126, G * 0.7152, B* 0.0722
  • Наконец, мы складываем все каналы вместе, чтобы получить значение относительной яркости от 0 до 1.

☯️ Коррекция контрастности

Нам также нужно немного скорректировать нашу calculateContrast() функцию. На данный момент это будет работать только в том случае, если значение яркости цвета переднего плана больше, чем значение яркости фона.
Но, конечно, контраст между двумя цветами одинаков, независимо от того, в каком направлении они находятся. Давайте обновим функцию следующим образом:

Мы делаем небольшую проверку, чтобы увидеть, происходит ли это, и исправляем результат, если это происходит. Наконец, мы добавляем 0,05 к каждому значению яркости в соответствии с формулой и округляем результат контрастности до двух десятичных знаков.

Теперь вернитесь в Figma и проверьте, работает ли он. Один из способов проверить это - создать группу прямоугольников в Figma и проверить их контраст с помощью онлайн-средства проверки контраста. Затем запустите и дважды проверьте наш плагин, чтобы убедиться, что мы получаем те же результаты.

💅 Преобразование RGB в шестнадцатеричный

На данный момент цвета, отправленные в наш пользовательский интерфейс, все еще имеют нормализованный формат RGB. Это немного сложно читать, и мы также знаем, что в конечном итоге мы хотим, чтобы пользовательский интерфейс отображал шестнадцатеричный код выбранных цветов. Итак, мы собираемся преобразовать их с помощью такой функции:

Я не собираюсь объяснять, как это работает, в основном потому, что я не потратил время на то, чтобы понять это 🤷‍♂️.

Затем в функции sendContrastInfo() мы передаем преобразованные цвета в пользовательский интерфейс следующим образом:

💯 Подсчет очков

Еще одна информация, которой не хватает, - это оценка контрастности WCAG. Если вы читаете руководство WCAG, в нем выделяются три различных уровня контрастности:

  • ✅ ✅ AAA: коэффициент контрастности 4,5: 1 или выше для текста размером более 18 пикселей. Или коэффициент контрастности 7: 1 или выше для текста размером менее 18 пунктов.
  • AA: коэффициент контрастности 3: 1 или выше для текста размером более 18 пунктов. Или коэффициент контрастности 4,5: 1 или выше для текста размером менее 18 пунктов.
  • 🆘 FAIL: коэффициент контрастности менее 3: 1 для текста размером более 18 пунктов. Или коэффициент контрастности менее 4,5: 1 для текста размером менее 18 пунктов.

Нам нужно инкапсулировать эту логику оценки в функцию, чтобы мы могли отправлять правильную оценку вместе с каждым сообщением:

Здесь мы переключаем значение параметра контрастности и обновляем оценки в зависимости от того, в какой диапазон он попадает. Важно ставить на первое место самые высокие значения, иначе вы получите неожиданные результаты. Также стоит отметить, что мы возвращаем оценки в объекте.

Обновите sendContrastInfo(), чтобы получить такие оценки:

Теперь мы должны увидеть объект scores, зарегистрированный в Figma, если мы протестируем наш плагин:

👻 Обработка непрозрачности

До сих пор мы сталкивались с удачным путем сравнения двух сплошных цветов друг с другом, но есть один крайний случай, с которым я хотел бы разобраться: непрозрачность. Это немного усложнит наш плагин, но я думаю, что это значительно улучшит взаимодействие с пользователем.

Чтобы рассчитать контраст с непрозрачностью, мы должны создать новый сплошной цвет, смешав два цвета следующим образом:

Затем мы вычисляем контраст между этим новым цветом переднего плана (#9D5186) и цветом фона (#E53030). Проблема в том, что если фон имеет непрозрачность, мы не можем определить, какой цвет позади, чтобы узнать, какой новый цвет сделать. Это означает, что мы можем надежно рассчитать контраст, только если непрозрачность находится на цвете переднего плана.

Поэтому для наших целей мы собираемся только проверить, имеет ли цвет переднего плана непрозрачность. Это нормально, потому что в 90% случаев это то, что люди хотят проверить, а в остальных 10% мы просто возвращаемся к сплошным цветам. Мы также собираемся добавить функцию swap, чтобы пользователи могли легко переключаться между передним и задним планом.

Первым шагом к решению этой проблемы является написание overlay() функции, которая принимает два цвета, которые мы хотим смешать, и прозрачность, которую мы будем использовать для их смешивания.

Так что же здесь происходит? Мы смешиваем два цвета в определенных пропорциях, используя значение альфа, чтобы определить эти пропорции. Давайте пройдемся по нему канал за каналом:

  • Допустим, значение нашего red канала было 255, а альфа - 80% или 0.8. Мы получаем 80% красного значения так: 255 * 0.8 = 204.
  • Затем мы берем red канал цвета фона, допустим, это 180, и получаем оставшиеся 20% следующим образом: 180 * 0.2 = 36.
  • Затем мы складываем эти два вместе, чтобы получить новое red значение 240. В результате мы получаем сплошной цвет, который составляет 80% переднего плана и 20% фона.

В функции calculateContrast() мы проверяем, меньше ли непрозрачность цвета переднего плана 1 (100% непрозрачность), и если это так, мы вызываем overlay(), чтобы создать новый цвет и присвоить его передний план:

Но откуда взялся alpha? Нам нужно получить его при изменении выделения и присвоить его другой глобальной переменной, чтобы позже поменять местами передний план и фон было проще:

Хорошо, это большая часть функциональности, которая нам нужна за кулисами. В следующей главе мы будем работать над отображением информации в пользовательском интерфейсе.