30 марта 2020 года разработчики Stack Overflow дали посетителям сайта возможность пользоваться бета-версией тёмной темы. Материал, перевод которого мы публикуем, посвящён рассказу о том, как создавалась тёмная тема Stack Overflow.
Баннер на Stack Overflow, который позволяет включить тёмную тему
Меня зовут Аарон Шеки. Я — руководитель отдела дизайна Stack Overflow. Я участвую в разработке дизайна компонентов интерфейса, лежащих в основе новых возможностей проекта.
Для начала — немного иронии. Лично я не являюсь любителем тёмных тем интерфейсов.
Я часто вижу, что уровень контраста тёмных интерфейсов оказывается слишком низким. В оформлении таких интерфейсов сложно использовать полный спектр цветов. То же относится и к имитации объёма с использованием теней, и к применению других визуальных эффектов. Когда я читаю светлый текст, расположенный на тёмном фоне, у меня устают глаза. С явлениями вроде симультанного контраста сложно иметь дело и при использовании светлых тем, а если применяется тёмная тема — то всё лишь усложняется.
Но я — тот человек, усилиями которого на Stack Overflow появилась тёмная тема.
Та работа, о которой я хочу рассказать, никогда не была направлена именно на разработку тёмной темы, несмотря на то, что многие пользователи уже давно просили оснастить ресурс такой темой. Но в ходе продвижения к тёмной теме нам пришлось решить множество задач. В частности — был модернизирован фронтенд-код Stack Overflow, была улучшена доступность контента. Работая над тёмной темой, мы получили стимул к более широкому использованию в проекте нашей дизайн-системы.
Можем ли мы и дать пользователям тёмную тему, и попутно открыть дорогу к улучшению доступности проекта? Мы ответили на этот вопрос положительно, сделав всё то, о чём я сейчас расскажу.
Исследование цветов
При создании исходных цветовых шкал для проекта, мы, что, вероятно, несколько наивно, брали некое цветовое значение и модифицировали его с использованием трансформаций, реализуемых с помощью Less. Так, например, мы могли объявить Less-переменную @red
и несколько раз сделать цвет темнее, воспользовавшись конструкцией darken(@red, 10%)
. То же касается и многократного осветления цвета с использованием tint(@red, 10%)
. Это способно дать цветовую шкалу, цвета в которой расположены в диапазоне от @red-050
до @red-900
с шагом в 10%.
Когда я впервые попробовал понять, как ресурс Stack Overflow мог бы выглядеть в тёмном режиме, я решил просто попробовать поменять белый фон на чёрный и «перевернуть» цветовые шкалы. При таком подходе цвет @red-050
становится цветом @red-900
, соответствующим образом меняются и цвета, расположенные между начальным и конечным цветами шкалы. Но сами эти цвета остаются теми же, что использовались раньше.
Светлая и тёмная цветовые шкалы для красного цвета
При таком подходе пострадала контрастность элементов. То, что получилось, содержало в себе то, что мне, в целом, не нравится в тёмных темах. Например, если внимательно присмотреться к самому тёмному варианту красного цвета, расположенному на тёмном фоне, будет видно, что цвет оказывается практически неразличимым. Подробнее об этом мы поговорим ниже.
Нам, определённо, нужно придумать что-нибудь получше
▍Начало работы: проектирование макета
Просто попытавшись обратить цветовые шкалы, я, на самом деле, ничего не добился. Этот шаг нельзя назвать реальным началом работы над тёмной темой. Поэтому я решил заняться разработкой макета и ручным подбором цветов в Figma. Я подбирал цвета, ориентируясь на то, как, в моём представлении, должен выглядеть Stack Overflow, и не думая о том, как новые цвета будут соотноситься с уже существующими. Уменьшение общего контраста было ключом к сохранению эффекта глубины в интерфейсе, к поддержке теней элементов, к применению полного спектра цветов.
Начало работы с проектирования макета позволило нам, в первую очередь, понять, к какому эстетическому эффекту мы стремимся, не обращая пока внимание на технические требования к проекту
▍Подбор улучшенного алгоритма работы с цветами
После того, как я подобрал для тёмной темы более светлый фон, у меня появилась возможность более глубоко исследовать цветовые шкалы. Сначала мне нужно было разобраться с некоторыми цветовыми проблемами существующей дизайн-системы, которые появлялись при применении светлой темы. В светлом конце спектра красные и жёлтые цвета выглядели не так, как мне хотелось бы. Некоторые цвета обладали самыми светлыми значениями, слишком близкими к белому цвету. Были и цвета, самые светлые значения которых были слишком тёмными.
Самый светлый вариант жёлтого цвета был неотличим от белого, а самый тёмный — неотличим от чёрного
У нас были проблемы и в тёмном конце спектра. Используя, в качестве фоновых, цвета @red-900
и @blue-900
, мы заметили, что эти цвета неотличимы от чёрного и друг от друга. Нам нужен был алгоритм, который дал бы нам цвета, основной тон которых различим и для самых светлых, и для самых тёмных их вариантов. Это позволило бы создавать компоненты с использованием данных цветовых значений.
Самые тёмные варианты наших цветов были неотличимы от чёрного и друг от друга
Создавая компоненты, реализующие уведомления, мы не могли пользоваться цветами из нашей дизайн-системы. Вместо этого нам нужно было подбирать на глаз собственные цвета.
Эти цвета прекрасны, но они не основаны на цветовых значениях из наших цветовых шкал
Я, для нормализации цветов, воспользовался замечательным инструментом ColorBox от Lyft Design. Вместо того чтобы менять цвета простейшим способом, линейно, с шагом в 10%, я применил кривые Безье. Это позволило достичь значительных улучшений в крайних положениях цветовых шкал.
После нормализации цветов в светлой части спектра я смог создать уведомления, цвета которых взяты из цветов нашей дизайн-системы
▍Тёмные версии цветов
После того, как мы привели в порядок светлую версию сайта, пришло время опробовать новые цвета на тёмном фоне. Я, в итоге, прибегнул к ручным настройкам того, что выдавал алгоритм подбора цветов, сделав это для сохранения фирменных цветов, которые использовались уже очень давно. Это позволило мне использовать новые цвета в продакшне и при этом обойтись без слишком резкого изменения внешнего вида проекта.
Полная нормализованная цветовая гамма
Реализация тёмной темы в интерфейсе Stacks
Если я хоть как-то рассчитывал на то, чтобы оснастить Stack Overflow тёмной темой, мне сначала надо было бы реализовать тёмную тему в нашей дизайн-системе Stacks, используя её в роли полигона для обкатки новой технологии.
▍Переменные
Мне нужно было преобразовать статические, компилируемые с помощью Less, шестнадцатеричные цветовые значения, в пользовательские CSS-свойства, рассчитанные на их использование во время работы программы. Речь идёт о том, чтобы цветовые значения хранились бы с использованием конструкций вида var(--red-500)
, а не с применением статических конструкций вида @red-500
. Это была интересная задача — и в рамках нашей дизайн системы, и в рамках всего сайта. Мы обычно брали единственное цветовое значение, вроде @red-500
, и делали его светлее или темнее для выделения разных состояний элементов (вроде состояний, принимаемых элементами при наведении на них мыши, или при получении ими фокуса), для использования в качестве фонового цвета или цвета границ элементов.
Если говорить о цветах каждой из кнопок, которых у нас много, о цветах каждого состояния каждой кнопки, то можно сказать, что эти цвета получались в ходе набора трансформаций единственного скомпилированного цветового значения. Это напоминало мне одну сцену из фильма «Игра на понижение». Там речь шла о возможности превратить десятимиллионную инвестицию в миллиарды долларов. Неудивительно, что такие «трансформации» привели к финансовому кризису.
Проблема обычных CSS-переменных заключается в том, что к ним нельзя применять Less-трансформации. Значения CSS-переменных вычисляются во время выполнения кода, поэтому конструкция наподобие darken(var(--red-500), 5%)
приводит к ошибке компиляции.
Всё это означало, что мне нужно было переработать код, отвечающий, например, за стилизацию кнопок. Сначала он выглядел так:
.s-btn {
color: @white;
background-color: @blue-600;
border: 1px solid darken(@blue-600, 5%);
&:hover {
background-color: darken(@blue-600, 5%);
border-color: darken(@blue-600, 10%);
}
}
Мне нужно было перейти на указание точных цветовых значений, описанных в нашей цветовой системе. В результате мне нужно было выйти на такой вариант кода:
.s-btn {
color: var(--white);
background-color: var(--blue-600);
border: 1px solid var(--blue-700);
&:hover {
background-color: var(--blue-700);
border-color: var(--blue-800);
}
}
При этом ясно, что одними кнопками всё не ограничивалось. Поэтому мне нужно было переработать стили всех компонентов, имеющиеся в Stacks. То же самое касалось и уведомлений, и всплывающих меню, и модальных окон, и ссылок, и многих других компонентов.
▍Браузерная совместимость
Если говорить о CSS-переменных, то их применение означало необходимость учёта их поддержки браузерами. В частности, их не поддерживает Internet Explorer 11 — браузер, о котором мы никогда не забывали. В итоге мы решили отказаться от поддержки IE11, избавившись от всех тех CSS-хаков, которые мы добавили в систему за годы её развития, принимая во внимание особенности данного браузера. Мы, кроме того, решили отправлять пользователям IE11 уведомления с предложением установить новый браузер. Это решение далось нам нелегко. Его претворение в жизнь потребовало недель рефакторинга.
▍Условные классы
IE11 больше меня не ограничивал и я смог работать с цветами в Stacks. Я решил добавить к элементу body
класс .theme-system
. Сделав это, я смог заменить светлые цвета на их тёмные эквиваленты с помощью соответствующего медиа-запроса. Кроме того, мы могли и полностью отказаться от медиа-запроса, просто добавив к body
класс .theme-dark
. Это позволило бы пользователям пользоваться тёмной темой сайта независимо от их системных настроек. Вот что у меня в результате получилось:
body {
--red-600: #c02d2e;
}
body.theme-system {
@media (prefers-color-scheme: dark) {
--red-600: #d25d5d;
}
}
body.theme-dark {
--red-600: #d25d5d;
}
Для достижения полной гибкости Stacks даёт возможность работать с атомарными цветовыми классами, которые применяются только тогда, когда включена тёмная тема. Подробности о поддержке CSS в Stacks можно почитать здесь. Например, добавив к элементу класс .d:bg-green-100
, дизайнер выражает следующую мысль: «В тёмном режиме нужно использовать для фона зелёный цвет bg-green-100
». Дополнительные условные классы, предназначенные для тёмного режима, позволяют нам убирать границы элементов, менять фоны, настраивать цвет текста. В этом твите можно увидеть интересный пример настройки цвета. Такая настройка иногда необходима в тёмном режиме. Хочу отметить, что одним из источников вдохновения при работе над тёмным режимом сайта для меня стал CSS-фреймворк Tailwind.
▍Использование тёмной темы в Stacks
После того, как тёмная тема была реализована в Stacks, мы решили добавить в верхнюю часть интерфейса системы кнопку, позволяющую переключаться между темами. У сотрудников компании должна была быть быстрая и удобная возможность переключения между разными цветовыми темами сайта.
Переключение между светлой и тёмной темами в Stacks
Реализация тёмной темы на главном сайте Stack Overflow
Задачи, связанные с реализацией тёмной темы в интерфейсе дизайн-системы Stacks я решил сравнительно легко. Рефакторинг, в ходе которого учитываются соображения будущего развития системы, облегчает то, что Stacks меньше, чем основной сайт, страдает от последствий ошибочных решений, принятых в прошлом. Для того чтобы оснастить тёмной темой Stack Overflow, мне нужно было обеспечить поддержку Less-переменных ради обратной совместимости решения. Это позволило нам организовать применение тёмной темы инкрементально, в отдельных частях интерфейса.
Так как большая часть нашего интерфейса, созданная после 2018 года, основана на Stacks, эта часть интерфейса получает, без дополнительных усилий с нашей стороны, и тёмную тему, и отзывчивые макеты. А как насчёт основной части интерфейса? Тут всё уже далеко не так просто.
▍Основные цвета сайта
Для начала мне нужно было внести в сайт самые крупные изменения, которые можно было сделать, не нарушив стандартную светлую тему Stack Overflow. Задачи, которые мне предстояло решить, заключались, в основном, в замене статических Less-переменных на эквивалентные им CSS-переменные. Сначала я применил стиль background-color: var(--white)
к фону сайта, заменив им стиль background-color: @white
. Благодаря этому основную площадь станиц теперь легко можно было перекрасить в фоновый тёмный цвет. Затем я проделал ту же операцию с цветами шрифтов и некоторых других элементов. Эта работа, в основном, заключалась в удалении большого объёма CSS-кода, так как, например, часто мы увлекались чрезмерной детализацией стилей, задавая цвета шрифтов дочерних элементов, хотя вполне могли бы обойтись цветовыми значениями, унаследованными от родительских элементов.
▍Демонстрация нового дизайна сотрудникам компании
После того, как я поработал над крупными блоками сайта, я попросил наших программистов Адама Леара и Ника Кравера помочь мне найти способ показа предварительного варианта тёмной темы сотрудникам Stack Overflow. Это позволило бы сотрудникам включать тёмную тему, пока очень далёкую от готовности, чтобы видеть, какие области сайта ещё нуждаются в доработке. Это, как я полагал, подвигнет их на помощь в доработке частей сайта с наибольшим трафиком. Это позволило бы мне преодолеть основной барьер на пути к новому дизайну. А именно — помогло бы интегрировать новый дизайн в существующую кодовую базу.
▍Кнопки
Если область сайта, работой над которой я занимался, была создана с использованием Stacks, то для того, чтобы подготовить её к использованию тёмного режима, нужно было решить не особенно много задач. Скажем, в ходе работы можно было решить, что где-то не нужна граница, или — что где-то в качестве фонового цвета нужен какой-то особый оттенок серого. Этим подготовка к применению тёмного режима могла и ограничиться. К сожалению, большая часть сайта была создана без использования Stacks.
Ярче всего это проявилось тогда, когда дело дошло до кнопок. За годы работы над сайтом в нём было применено множество реализаций кнопок. Это особенно расстраивало, так как мы стилизовали сами элементы button
. Это означает, что любому элементу button
, или элементу, оформленному как input type=«button»
, назначался стиль, применяемый по умолчанию. Этот стиль отличается очень высоким уровнем специфичности и основан на устаревшем наборе правил.
Вышеозначенный факт привёл к необходимости проведения большого рефакторинга, который всё ещё продолжается. Он нацелен на удаление CSS-стилей уровня элементов button
и на замену их на эквиваленты из Stacks. В частности, речь идёт о том, что сотни элементов input type=«submit»
нужно заменить на <button type="submit" class="s-btn s-btn__primary">
. Кроме того, что добавило работы, отмечу, что мы часто привязывали к визуальным селекторам JavaScript-функционал. Если поменять классы, влияющие на внешний вид элемента, это может поломать функционал кнопки. В результате, в ходе переработки тысяч кнопок, мне нужно было сначала добавить к ним классы вида js-
, подключить к ним обработчики, а затем избавиться от старых стилей, задающих внешний вид кнопок.
Это в итоге привело меня к удалению большей части старых классов кнопок, что позволило кнопкам правильно менять цвета при включении тёмной темы.
▍Заголовок сайта
Ещё больше усложнило задачу то, что заголовок, используемый на всём сайте, может работать в разных режимах. Речь идёт о светлом и тёмном режимах, и о режиме поддержки особой темы. В варианте сайта для команд (Stack Overflow for Teams) и для корпоративных систем принудительно используется тёмная тема заголовка. Кроме того, в командной версии используется цвет, задаваемый цветом командного аватара. Как и многие другие наши компоненты, CSS для заголовка сайта предусматривает наличие единственного цвета, от которого зависит то, будет ли заголовок светлым или тёмным. Этот цвет потом, с использованием сложных Less-инструкций, трансформируется, что позволяет получать разные его варианты. Однако мы не могли поступить так, как поступили, перерабатывая код дизайн-системы. То есть — не могли просто выбросить весь старый CSS и заменить его на код, в котором используются CSS-переменные. Дело в том, что наши корпоративные клиенты, на самом деле, полностью, с помощью тем, настраивают внешний вид заголовка. При этом цветовые варианты генерируются на базе единственного цвета.
Светлый заголовок
Тёмный заголовок
Командный заголовок
В случае со светлым заголовком Stack Overflow нам нужно было найти механизм, позволяющий узнавать о том, задан ли некий цвет с помощью CSS-переменной, или с помощью статического шестнадцатеричного значения. Если цвет задан с помощью CSS-переменной, то мы могли бы полностью пропустить его Less-трансформацию, создавая заголовок, который мог бы менять цвета, основываясь на параметрах тёмной темы. Если вместо этого использовалась статическая Less-переменная, нужно было оценить цвет на предмет того, является ли он светлым или тёмным, и создать соответствующий заголовок.
В итоге мы вышли на такой подход:
& when ( iscolor(@theme-topbar-background-color) ) {
@theme-topbar-style: if(luma(@theme-topbar-background-color) >= 50%, light, dark);
}
& when not ( iscolor(@theme-topbar-background-color) ) {
@theme-topbar-style: automatic;
}
Затем на основе полученного значения (automatic
, light
или dark
) создаётся правильный заголовок.
▍Теги
Если бы я мог дать всего один совет тем, кто занимается проектированием компонентов, то он звучал бы так: «Не описывайте в компонентах то, что влияет на их расположение на странице». Другими словами — то, какое пространство разделяет компоненты, должен определять контекст, в котором они применяются. Не надо задавать подобные вещи в самом компоненте. В ранних версиях Stack Overflow было решено, что к компонентам post-tag
должны применяться внешние отступы. При этом теги, как и кнопки, оказались подвержены проблеме, связанной с JavaScript. Для того чтобы ещё сильнее всё усложнить, большинство тегов генерировалось с помощью единственного вспомогательного метода.
Рефакторинг тегов предусматривал замену post-tag
-компонентов на s-tag
-компоненты, рассчитанные на применение разных тем. Эта работа предусматривала и внесение изменений в JavaScript, учитывающих использование js-tag
. К тому же, нужно было изменить метод, отвечающий за генерирование тегов, сделав так, чтобы он принимал бы произвольные классы, влияющие на размещение элементов. Дело в том, что в определённых контекстах теги может понадобиться поместить во flex-макет, сделав это вместо того, чтобы полагаться на заранее заданные внешние отступы (или вместо того, чтобы бороться с такими отступами).
▍Стилизация публикаций
Основной объём Stack Overflow составляют публикации, сделанные пользователями. Эти публикации, а именно — вопросы, ответы, комментарии, оформляются с помощью Markdown-разметки. Во время запуска Stack Overflow Markdown-разметка была сравнительно новой технологией.
За годы развития веба выработались некоторые стандартные способы вывода чего-то наподобие заголовков и цитат. Внедрение тёмной темы представляло собой отличный момент пересмотра нашего подхода к форматированию публикаций. Самым неоднозначным вопросом в этом деле оказалось форматирование цитат.
Изначально цитаты были оформлены с использованием мощного жёлтого фонового цвета, из-за которого снижалась контрастность самой цитаты. Жёлтый цвет, кроме того, вызывает определённые проблемы при его выводе его на тёмном фоне. В итоге мы перешли к общепринятому способу вывода цитат, воспользовавшись узкой серой вертикальной полосой для их выделения.
▍Стилизация кода
На страницах Stack Overflow, что вполне понятно, выводится много фрагментов кода. Цвета, которые мы использовали для подсветки синтаксиса, не имели ничего общего с нашими фирменными цветами. У меня есть сильное подозрение, что эти цвета являются наследием первой использованной в проекте библиотеки для подсветки синтаксиса. Я, проанализировав эти цвета, решил подвергнуть подстветку синтаксиса глубокому редизайну. В результате цвета подсветки были заменены цветами из нашей дизайн-системы. Это относится и к светлой, и к тёмной темам. И сделано это так, чтобы то, что получилось, не отличалось бы кардинальным образом от того, что было раньше.
Результаты
Светлая тема и бета-версия тёмной темы, выпущенная 30 марта
Благодаря тому объёму работ по рефакторингу, который мы проделали, двигаясь к тёмной теме, мы получили возможность без особых сложностей вносить во внешний вид проекта достаточно большие изменения. Основываясь на том, что у нас есть теперь, мы можем, например, разработать высококонтрастную тему сайта, повышающую его доступность.
Создание тёмной темы стало результатом фундаментального сдвига в подходе к дизайну Stack Overflow. В частности, речь идёт о применении дизайн-системы, продвижением которой я занимаюсь уже год. А создание тёмной темы стало для меня отличной возможностью перестроить многие части сайта. Это — первый из многих проектов, которые позволят повысить доступность Stack Overflow.
Если не учитывать отказ от поддержки IE11, работа над тёмной темой началась в июле 2019 года с этого PR, направленного на исследование ситуации. До этого, в апреле 2019, были обсуждения, касающиеся тёмной темы. В октябре 2019 в продакшн отправился экспериментальный вариант тёмной темы. А потом, после того, как было сделано минимум 60 PR, бета-версия тёмной темы была представлена пользователям. Случилось это 30 марта 2020 года.
Уважаемые читатели! Пользуетесь ли вы Stack Overflow? Нравится ли вам их новая тёмная тема?
Автор: ru_vds