От автора: недавно я провел некоторое время со стартапом Kite. Я работал над типографикой своих инструментов. Среди их группы умных утилит, помогающих программистам кодировать, центральным является родное / веб-приложение Kite Copilot.
Оно выглядит так:
Однажды рано утром программист, использующий Copilot, написал нам предложение добавить вторую тему темного цвета: способ убедить приложение использовать легкий текст на более темном фоне. Это имело смысл. Темная тема — это то, что предпочитают многие программисты — действительно, по умолчанию во многих современных редакторах, таких как Atom или Visual Studio Code. Прямо рядом с редактором с темной темой, Copilot может почувствовать себя одетым в неправильную сторону:
Я начал задаваться вопросом, можно ли в тот же день добавить темную тему, обещая себе, что я перейду к другим вещам на следующий день, дождь или блеск, закончу ли я или нет.
Это история о том, как несколько новых вариантов CSS были собраны вместе, как кучка супергероев в летнем блокбастере, и позволили этому проекту действительно осуществиться реально за 1 день.
Переменные CSS
Я сразу понял, что этот проект не может произойти без переменных CSS , и я был в восторге от того, чтобы попробовать их в первый раз в родном CSS (я только знал их раньше любезности LESS ). Я начал с превращения всех существующих жестко закодированных цветов в переменные, а затем уменьшил количество переменных до абсолютного минимума. Это выглядело примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Before .main { background-color: #eef4f7; color: #0a244d; } .docs { color: #0c2959; } After html { --background: #eef4f7; --text-color-normal: #0a244d; } .main { background: var(--background); color: var(--text-color-normal); } .docs { color: var(--text-color-normal); } |
Однако я не ожидал систематизации всего в легкой теме, и только потом придумал ночную альтернативу. Фактически, я сделал полную противоположность. Я создал простейшие леса второй темы … и сразу же включил его. Причиной этого было просто: этот подход упростил возможность смотреть, какие цвета среди десятков файлов CSS (большинство из них я никогда не просматривал раньше) я пропустил и еще не превратил в переменные. Все, что было отключено, было легко обнаружить в все более темной и темной вселенной:
Я также очистил несколько других смежных областей. Я удалил любую непрозрачность, которая была сопряжена с цветами, и испекла легкость в цветах. Я удалил все альфа-значения (rgba). Это облегчило мне доверие к ним, так как некоторые из полупрозрачных цветов могли стать фоном поверх других элементов с другими цветами фона, и было невозможно предсказать их взаимодействие.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Before .home { color: #0a244d; } .home__version { opacity: 0.7; } After html { --text-color-normal: #0a244d; --text-color-light: #8cabd9; } .home { color: var(--text-color); } .home__version { color: var(--text-color-light); } |
Я сохранил opacity только для отключенных элементов пользовательского интерфейса — его присутствие было четко определено и упростило ситуацию.
Наконец, я также создал переменную для теней, используемых на popovers, так как мне нужны были тени, которые будут более заметными в темной теме.
Выражение цветов
Затем я взял переменные на один шаг вперед. Я не хотел предполагать, что цвета всегда будут происходить из мира дизайна, а работа инженеров будет заключаться только в их подключении. Была и практическая причина: игра с цветами в реальном времени путем редактирования файла казалась лучшей способ оптимизации палитры.
Итак, чтобы облегчить жизнь каждого (начиная с моей собственной), я перешел к описанию всех цветов как HSL:
1 2 3 4 5 6 7 8 9 10 11 12 |
Before html { --text-color-normal: #0a244d; --text-color-light: #8cabd9; } After html[data-theme='dark'] { --text-color-normal: hsl(210, 10%, 62%); --text-color-light: hsl(210, 15%, 35%); --text-color-richer: hsl(210, 50%, 72%); --text-color-highlight: hsl(25, 70%, 45%); } |
Я нахожу HSL наиболее приемлемым способом навигации по миру цветов, три компонента имеют более четкие обязанности, чем непрозрачные и произвольные циферблаты RGB (где яркость — сложная формула, а не просто число, а насыщенность очень глубоко погребена).
Но было другое преимущество: я мог бы добавить вторичные переменные для основных оттенков цветовой темы.
1 2 3 4 5 6 |
html[data-theme='dark'] { --hue: 210; /* Blue */ --accent-hue: 25; /* Orange */ --text-color-normal: hsl(var(--hue), 10%, 62%); --text-color-highlight: hsl(var(--accent-hue), 70%, 45%); } |
Я хотел облегчить для человека, идущего за мной, понять отношения между цветами. Даже без переменных оттенка HSL упростит работу; все цвета, начинающиеся с hsl(210 были бы сразу узнаваемы как семья, но, сделав еще 210 немного затянули винты. Это был мой способ сказать: «Не стесняйтесь экспериментировать с цветами, но акт добавления другого оттенка должен быть умышленный».
Кроме того, изменение значения оттенка было еще одним способом проверки целостности темы. Любой цвет, который не меняется, означает, что цвет случайно оставлен позади:
Я подумал, что идем дальше, выражая некоторые цвета как вариации на других. Но это было похоже на перебор. Немного неэлегантный способ CSS выражает переменные, в сочетании с необходимостью calc() сделает этот CSS намного менее удобочитаемым.
Итак, что в итоге получилось:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
html[data-theme='dark'] { --hue: 210; /* Blue */ --accent-hue: 25; /* Orange */ --text-color-normal: hsl(var(--hue), 10%, 62%); --text-color-light: hsl(var(--hue), 15%, 35%); --text-color-richer: hsl(var(--hue), 50%, 72%); --text-color-highlight: hsl(var(--accent-hue), 70%, 45%); --link-color: hsl(var(--hue), 90%, 70%); --accent-color: hsl(var(--accent-hue), 100%, 70%); --error-color: rgb(240, 50, 50); --button-background: hsl(var(--hue), 63%, 43%); --button-text-color: black; --background: hsl(var(--hue), 20%, 12%); } |
Переключение между темами
Тема была применена к атрибуту data поверх <html>. Класс тоже будет в порядке, но в моих классах классы похожи на флажки, тогда как атрибуты данных действуют скорее как переключатели. Это было похоже на спорном различия в этот самый момент — у нас было только две темы — но я хотел бы думать немного вперед.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Definition in CSS html { --hue: 210; /* Blue */ --text-color-normal: hsl(var(--hue), 77%, 17%); ... } html[data-theme='dark'] { --text-color-normal: hsl(var(--hue), 10%, 62%); ... } Invocation in JavaScript document.documentElement.setAttribute('data-theme', 'dark') document.documentElement.setAttribute('data-theme', 'light') |
(Обратите внимание, что тема по умолчанию была выражена без каких-либо классификаторов. Это гарантировало, что если что-то пошло не так, и атрибут данных исчезнет, все будет оставаться читаемым.)
Это было совершенно функционально, но переключение темы назад и вперед стало крутым. Это был, по сути, переключатель освещения, но была причина, по которой я установил переключатели освещения и затухания света во всех комнатах, в которые я тратил значительное количество времени. И поэтому я также установил один в CSS Copilot:
1 2 3 4 5 6 7 |
html.color-theme-in-transition, html.color-theme-in-transition *, html.color-theme-in-transition *:before, html.color-theme-in-transition *:after { transition: all 750ms !important; transition-delay: 0 !important; } |
Этот класс, color-theme-in-transition, эффективно сообщает браузеру «буквально любой стиль, который меняется с этого момента, делает его переходным».
Применяется все время, и каждое случайное изменение цвета или переход на смену станет кошмаром slo-mo, принадлежащим больше в фильме Зака Снайдера, чем в инструменте производительности для программистов. Но если бы мы должны добавить этот класс тактически за минуту до смены темы и удалить, как только переход закончится, это даст нам прекрасный кроссфейд:
1 2 3 4 5 |
document.documentElement.classList.add ( 'цвет-тема-в-перехода') document.documentElement.setAttribute ('data-theme', theme) window.setTimeout (function () { document.documentElement.classList.remove ( 'цвет-тема-в-перехода') }, 1000) |
Возможно, это была моя любимая часть проекта, потому что мы работали вместе в разных отделах CSS. Изменение атрибута привело к тому, что новый набор переменных автоматически заменил старые. После этого все новые цвета внутри этих переменных будут размещаться во всех местах, где они были использованы … но не раньше, чем другой отдел CSS будет держать их аккуратно на месте, растягивая то, что в противном случае было бы проблеском глаза на лучшая часть секунды. Изменился только один новый класс и один атрибут, и сложный механизм CSS ожил в один волшебный момент.
CSS часто чувствует себя неуклюжим. Я не могу сказать, что я поклонник того, как вы выражаете переменные в нем — это выглядит странно, и я продолжаю ставить двойные тире в точном неправильном месте. Но когда CSS идет, это происходит. И в этот самый момент было здорово смотреть, как это происходит.
(Загвоздка: это очень хорошо работало в Firefox, но Chrome иногда все еще боролся, слишком поздно переходил на некоторые части или вообще не появлялся).
Темизация векторных иконок
Цветовые переменные внимательно следили за текстом. Но затем наступил страшный момент, когда дело касалось визуальных элементов. Букет иконографии в Copilot Kite существовал как вектор SVG в качестве фона:
1 2 3 4 5 |
.sidebar__icon__settings { width: 2rem; height: 2rem; background-image: url(./icon-settings.svg); } |
Насколько я знаю, вы можете раскрашивать SVG только с помощью CSS в некоторых случаях — когда вы внедряете их встроенные или когда используете их в качестве изображений. Но вы не могли этого сделать в этом сценарии. И без него значки в темной теме выглядели бы плохо, если бы не исчезли прямо:
К счастью, был другой способ, чем создание двух версий всех файлов SVG. Я мог бы использовать маску вместо фона:
1 2 3 4 |
mask-image: url(./icon-refresh-white.svg); mask-repeat: no-repeat; mask-position: center; background: blue; |
То, что означает вышеупомянутый код — «превратить всю вещь в синий цвет, но затем отрезать все, что не покрывается формой SVG». Если SVG имеет прозрачный фон, он работает красиво. И в этом случае он сделал:
И чтобы сделать переход еще проще, синтаксис маски и фона в основном взаимозаменяем — с такими вещами, как background-repeat, background-position и background-size с идеальными доппельгерами на стороне маски.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Before background-size: contain; background-repeat: no-repeat; background-position: center; background-size: 1rem; background-image: url(./icon-back.svg); After mask-size: contain; mask-repeat: no-repeat; mask-position: center; mask-size: 1rem; mask-image: url(./icon-back.svg); background-color: var(--link-color); |
Конечно, есть оговорки. Вы можете использовать только один цвет. Плюсы маски, поддерживаемые относительно хорошо, по-прежнему требуют префикса для webkit.
Самое главное, применение маски не позволяет вам делать что-либо еще в одном элементе: любая граница, любой контур, любой текст тоже маскируется. В нескольких случаях мне пришлось творчески понизить значок до :before или :after псевдоэлемента, который, к счастью, может быть замаскирован независимо от основного.
Без этого метода проект, вероятно, остановится на своем пути. Дело в том, что я боялся, что это все равно, так как на моем пути был один вид визуальных эффектов.
Изображения
Kite также имеет регулярные растровые изображения, перемежающиеся через некоторую документацию разработчика. Они выглядели примерно так:
Видеть большой прямоугольник белого скрола на виду было не просто эстетически неприятным. Я представил себе, что, хотя для некоторых людей темная тема — просто визуальный выбор, некоторые — даже многие — могут позволить ему лучше заботиться о своих глазах. Я не хотел, чтобы тема набрала 95%, но потом ведите себя как раздражающий кинозритель, вытаскивающий свой смартфон на максимальной яркости, и текстовые сообщения в середине вышеупомянутого фильма Зак Снайдера.
К счастью, современный CSS избавил меня от повторного редактирования всех изображений вручную или написания сценария для обработки и повторного сохранения. Я мог бы использовать фильтр CSS, чтобы легко инвертировать каждое изображение:
1 2 3 |
html[data-theme='dark'] img { filter: invert(100%); } |
Однако изображение выглядело не совсем правильно, с совершенно разными цветами. Но я вспомнил трюк, который я взял в Photoshop несколько лет назад: инвертируйте изображение, а затем поверните его оттенок на полпути, и изображение будет выглядеть гораздо более естественным. Это не так хорошо работает для фотографий, но для любых диаграмм это в основном безупречно. К счастью, это были фотографии, с которыми вы столкнулись в приложении Kite.
И, к счастью, еще раз, был также фильтр CSS, который позволил мне сделать то же самое автоматически:
1 2 3 |
html[data-theme='dark'] img { filter: invert(100%) hue-rotate(180deg); } |
И тогда я мог бы пойти еще дальше! Еще одна вещь теперь доступна в CSS; используя режим смешивания, я мог бы сделать визуальные ощущения еще более у себя дома — благодаря тому, чтобы его темные пиксели эффективно трансформировались:
1 2 3 4 |
html[data-theme='dark'] img { filter: invert(100%) hue-rotate(180deg); mix-blend-mode: screen; } |
Я мог бы даже вернуть режим смешивания к исходной теме и сделать ее лучше ( multiply на противоположность screen, так что она будет работать и для вывода на светлый фон). Еще раз, добавление темной темы рикошетом в улучшение оригинальной тоже.
1 2 3 4 5 6 7 |
img { mix-blend-mode: multiply; } html[data-theme='dark'] img { filter: invert(100%) hue-rotate(180deg); mix-blend-mode: screen; } |
И все. Эти несколько строк CSS сделали проект завершенным. (В зависимости от ваших потребностей вы можете просто уменьшить яркость или контрастность, а не инвертировать — оба доступны также через фильтры CSS).
Темизация полосы прокрутки
Ну, почти. Если изображения и значки сохраняли свои яркие ливреи и должны были затемнить, мы столкнулись с противоположной проблемой с автопроливной панелью Mac — она была черной (и в основном невидимой) даже в темном режиме.
Сначала я начал экспериментировать с винтажными значениями CSS-полос прокрутки Webkit и получил где-то наполовину приличный, но вскоре мы узнали, что он пришел со страшной ценой: любое изменение на них заставляло полосы прокрутки постоянно отображаться, даже если ваш параметр Mac был установлен на спрячьте их, пока вы не прокрутите список.
Я вернулся к чертежной доске. Оказалось, мне не нужно беспокоиться о стилизации полосы прокрутки, но убедить полосу прокрутки в стилизации. Полоса прокрутки Mac может фактически превращать свет, когда это необходимо. За исключением того, что мы не прокручивали всю страницу, а только меньший элемент внутри нее, и почему-то это путало внутренний хамелеон прокрутки. Применяя фон снова прямо к этому прокручиваемому элементу, даже если он лишний, заставил полосу прокрутки понять, что она станет легче:
CSS]
.docs-page__content {
background: var(—background);
overflow-y: auto;
}
Я также убедился, что полоса прокрутки приклеена к краю. Это раздражает, когда панель прокрутки authiding сидит в середине и перекрывает контент, и тем более в темной теме. (И это затрудняет захват, если это по краю экрана!)
Решение было таким же простым, как и отладка от родительского элемента, который обертывал прокручивающийся контент и вместо этого перемещал его внутри элемента прокрутки:
Высокая контрастная тема
Я также хотел расширить этот проект в другом направлении. Я добавил еще одну тему — высокую контрастность:
Я буду первым, кто признает, что это не самая лучшая тема с высоким контрастом. Не только это: я даже не совсем уверен, что означает хорошая высокая контрастная тема. Но я чувствую, что важно хотя бы начать, чтобы послать сообщение о том, что цвета не только об эстетике, моде или комфорте — людям с нарушениями зрения, они могут быть разницей в возможности использовать приложение или нет.
Переменные для высокой контрастной темы красивы в своей простоте …
1 2 3 4 5 6 7 8 9 10 11 12 13 |
html[data-theme='high-contrast'] { --text-color-normal: white; --text-color-light: white; --text-color-richer: white; --text-color-highlight: white; --link-color: white; --bright-color: white; --error-color: white; --button-background: white; --button-text-color: black; --background: black; --popup-background: black; } |
… и новая тема также стала еще одним неожиданным испытанием для целостности моего набора переменных и того, как я их применял. Если тема с высоким контрастом закончилась тем, что на белом фоне был белый текст — или черный на черном — это означало, что в моем мышлении что-то пошло не так, и что контраст по любой из двух других тем, вероятно, тоже был в беде.
Таким образом, я еще раз испытал этот проверенный эффект: доступность дает универсальность. Попытка повысить доступность не только приносит пользу определенной группе людей; это приносит пользу всем.
(Я верю, что правильная тема с высоким контрастом должна иметь больше границ и других элементов для зависания глаз. Пожалуйста, дайте мне знать, если вы знаете. Единственное, что я изменил, это тень, становящаяся большей частью границы, как показано ниже )
1 2 3 4 5 6 7 8 9 |
html { --popup-shadow: 0 5px 16px rgba(0, 0, 0, .25); } html[data-theme='dark'] { --popup-shadow: 0 5px 32px black; } html[data-theme='high-contrast'] { --popup-shadow: 0 0 5px white, 0 0 5px white; } |
Вот и все. В конце дня я закончил темную тему, и через несколько дней я провел еще несколько часов, добавив пару последних штрихов и исправлений ошибок.
Я был поражен тем, что все это может произойти только с помощью CSS: цвета, переходы, векторы и даже изображения:
Мне любопытно, есть ли у вас другие трюки? Любые другие новые свойства CSS, которые могут пригодиться? Или другое понимание темных тем? Пожалуйста, комментируйте этот пост.
Автор: Marcin Wichary
Источник: //medium.com/
Редакция: Команда webformyself.