[Перевод] Создание тёмной темы для Stack Overflow

30 марта 2020 года разработчики Stack Overflow дали посетителям сайта возможность пользоваться бета-версией тёмной темы. Материал, перевод которого мы публикуем, посвящён рассказу о том, как создавалась тёмная тема Stack Overflow.

61f9d2662c30a95f29c885ac6c3180ee.png


Баннер на 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, соответствующим образом меняются и цвета, расположенные между начальным и конечным цветами шкалы. Но сами эти цвета остаются теми же, что использовались раньше.

4dec023214649262a2a743d8e32e6de0.png


Светлая и тёмная цветовые шкалы для красного цвета

При таком подходе пострадала контрастность элементов. То, что получилось, содержало в себе то, что мне, в целом, не нравится в тёмных темах. Например, если внимательно присмотреться к самому тёмному варианту красного цвета, расположенному на тёмном фоне, будет видно, что цвет оказывается практически неразличимым. Подробнее об этом мы поговорим ниже.

3ddaa47fcedcb5bc32c10e43d2079a7b.png


Нам, определённо, нужно придумать что-нибудь получше

▍Начало работы: проектирование макета


Просто попытавшись обратить цветовые шкалы, я, на самом деле, ничего не добился. Этот шаг нельзя назвать реальным началом работы над тёмной темой. Поэтому я решил заняться разработкой макета и ручным подбором цветов в Figma. Я подбирал цвета, ориентируясь на то, как, в моём представлении, должен выглядеть Stack Overflow, и не думая о том, как новые цвета будут соотноситься с уже существующими. Уменьшение общего контраста было ключом к сохранению эффекта глубины в интерфейсе, к поддержке теней элементов, к применению полного спектра цветов.

7f6abfd73f890a0c221fafada3eefa8b.png


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

▍Подбор улучшенного алгоритма работы с цветами


После того, как я подобрал для тёмной темы более светлый фон, у меня появилась возможность более глубоко исследовать цветовые шкалы. Сначала мне нужно было разобраться с некоторыми цветовыми проблемами существующей дизайн-системы, которые появлялись при применении светлой темы. В светлом конце спектра красные и жёлтые цвета выглядели не так, как мне хотелось бы. Некоторые цвета обладали самыми светлыми значениями, слишком близкими к белому цвету. Были и цвета, самые светлые значения которых были слишком тёмными.

554d5f86ce75ada85b706b3604bb904b.png


Самый светлый вариант жёлтого цвета был неотличим от белого, а самый тёмный — неотличим от чёрного

У нас были проблемы и в тёмном конце спектра. Используя, в качестве фоновых, цвета @red-900 и @blue-900, мы заметили, что эти цвета неотличимы от чёрного и друг от друга. Нам нужен был алгоритм, который дал бы нам цвета, основной тон которых различим и для самых светлых, и для самых тёмных их вариантов. Это позволило бы создавать компоненты с использованием данных цветовых значений.

5704e59bdc120461244c34f4dc678bea.png


Самые тёмные варианты наших цветов были неотличимы от чёрного и друг от друга

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

3fc1e4a5627c6bec05aefae3de608e15.png


Эти цвета прекрасны, но они не основаны на цветовых значениях из наших цветовых шкал

Я, для нормализации цветов, воспользовался замечательным инструментом ColorBox от Lyft Design. Вместо того чтобы менять цвета простейшим способом, линейно, с шагом в 10%, я применил кривые Безье. Это позволило достичь значительных улучшений в крайних положениях цветовых шкал.

3e958e6ffcf012c47397c0a67afb58a3.png


После нормализации цветов в светлой части спектра я смог создать уведомления, цвета которых взяты из цветов нашей дизайн-системы

▍Тёмные версии цветов


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

eb18f000f7742e215c90a7c9be062795.png


Полная нормализованная цветовая гамма

Реализация тёмной темы в интерфейсе 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, мы решили добавить в верхнюю часть интерфейса системы кнопку, позволяющую переключаться между темами. У сотрудников компании должна была быть быстрая и удобная возможность переключения между разными цветовыми темами сайта.

cef577bd304db402329b23ee9aa740e4.gif


Переключение между светлой и тёмной темами в 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» нужно заменить на