Наш опыт переезда на адаптивный UI-кит
Всем привет! Меня зовут Дмитрий Беляев, я работаю frontend-разработчиком в отделе медиапроектов Mail.Ru Group. Вместе с нашей командой мы занимаемся разработкой и поддержкой 13-ти вертикальных проектов различной тематики. До недавнего времени каждый из них довольно сильно отличался от остальных как в плане дизайна, так и в плане используемых технологий. Поскольку сейчас они развиваются в схожих направлениях, к нам все чаще стали приходить менеджеры с вопросом: «На одном из проектов недавно выкатили фичу N, можем ли запустить аналог для нашего проекта на следующей неделе / завтра / вчера?», после которого мы начинали копаться в особенностях верстки очередного проекта, натыкаться на новые подводные камни, не считая того, что мы повторно решали одни и те же задачи. Подобные ситуации начали наталкивать всех на мысль об унификации, что позволило бы не только повысить узнаваемость проектов, но и сократить время на решение рабочих задач.
В это же время начинали разрабатываться мобильные версии проектов для touch-устройств, и было решено опробовать новый подход к разработке именно на них. Структура страниц мобильной версии сайта обычно намного проще, чем аналогичные страницы десктопных версий и, соответственно, в случае ошибки при выборе метода решения той или иной задачи мы тратили бы меньше времени на исправление. Отдел проектирования интерфейсов подготовил для нас UI-kit, содержавший в себе огромное количество прототипов и макетов для отдельных блоков и лэйаутов типовых страниц наподобие листингов новостей, статей, комментариев. Дополнительно были отрисованы уникальные элементы интерфейса, необходимые только для некоторых отдельных проектов, например, блок рецепта на Леди или калькулятор на Курсах. По ним была разработана собственная библиотека блоков, сверстанных на основе методологии BEM с использованием доработанных нами BEM-tools в связке с шаблонизатором Fest и Stylus’ом.
Подход с использованием такой библиотеки блоков себя отлично зарекомендовал. Он позволил нам избежать необходимости множественного дублирования одинакового кода и сильно снизил трудозатраты. Он также порадовал и дизайнеров, поскольку им не приходилось больше отслеживать соответствие дизайна каждого блока на каждом проекте, достаточно было проверить блок на любом из проектов, ведь теперь использовалась единая реализация общих блоков, хранящаяся в едином репозитории.
После успешного запуска новых мобильных версий мы решили использовать этот подход и при предстоящих редизайнах десктопных версий проектов. Благодаря единому стилю и абстрактной реализации блоков после первых запусков мы уже могли начинать верстать большую часть страниц на основе прототипов дизайна, без необходимости детальной отрисовки на макетах каждого элемента дизайна. Это означало и уменьшение количества ресурсов, затрачиваемых дизайнерами. Помимо столь захватывающих перспектив на пути внедрения данного подхода нас поджидали непредвиденные трудности. И сегодня я хочу рассказать вам о том, как мы их преодолевали.
Так как практика применения UI-kit и сопутствующих технических решений началась с мобильных версий, страницы которых в большинстве случаев состоят из простых элементов, часто получалось, что дизайн этих элементов во многом схож. Казалось вполне логичным реализовать их в виде одного общего блока, который имел бы стандартный вариант вывода и набор модификаторов под разные кейсы. Изначально все работало просто отлично, но, с запуском новых проектов количество вариантов использования блока неуклонно росло, а логика работы уже существующих вариантов становилась все сложнее. На поддержку такого блока приходилось тратить все больше времени. Например, казалось бы, простая задача – подсвечивать ссылки новостей, которые пользователь уже просматривал, вылилась в добавление нового модификатора и прописывания его всем новостным сущностям этого блока.
При таких обстоятельствах естественным стало решение разделить все эти кейсы на отдельные блоки даже в том случае, если они отличаются друг от друга семантически при похожем дизайне. В противном случае могла быть утеряна одна из основных фич подхода – простая поддержка китового дизайна с возможностью без труда применять и поддерживать его на всех проектах. Так у нас появились отдельные блоки, отвечающие за вывод новостей, комментариев, консультаций, информации об учреждениях и так далее.
В поисках красивых шрифтовых решений для десктопных версий проектов изначально были выбраны 4 начертания шрифта Roboto и нарисована шрифтовая сетка, содержавшая в себе стандартные варианты использования шрифтов при создании макетов. На деле же, мы довольно быстро столкнулись с проблемой человеческого фактора, когда по случайности в макетах оказывались новые размеры в уже стандартизированных блоках. Это привело нас к необходимости создать отдельный конфиг для описания параметров шрифтов. Изначально все варианты их использования были разделены в конфиге на семантические группы: заголовки, наборный текст, небольшие подписи. Каждому из размеров соответствовало какое-либо название ключа, но количество начертаний в сетке постоянно изменялось. Поддерживать его в таком виде стало проблематично и мы решили хранить информацию о шрифтах в общем хэше, разбитом на группы по начертаниям.
$font['sets'] = defaults({
light: {
_default: {
font-weight: $font.weight.light
},
'9': {
font-size: 9px,
line-height: 12px,
_media: {
font-size: 11px,
line-height: 16px
}
},
'13': {
font-size: 13px,
line-height: 16px,
_media: {
font-size: 15px,
line-height: 20px
}
},
'15': {
font-size: 15px,
line-height: 20px,
_media: {
font-size: 17px,
line-height: 24px
}
},
...
Для работы с этим конфигом был написан небольшой миксин в stylus, через который стали прописываться все размеры шрифтов на проектах. При этом для любого элемента его можно вызвать в адаптивном варианте, передав дополнительный параметр, в зависимости от которого увеличатся размеры для больших разрешений экрана.
set(hash, media = false)
if (media)
if ('_media' in hash && type(hash._media) is 'object')
for $size in 'medium' 'large'
+forScreen($size)
for $attr-name, $attr-value in hash._media
{$attr-name}: ($attr-value)
for $attr-name, $attr-value in hash
if ($attr-name != '_media')
{$attr-name}: ($attr-value)
.block__element
set: $font.sets.light[‘15’] true
В дальнейшем это решение нам очень сильно помогло, когда во время редизайна первого десктопного проекта с использованием UI-kit появились проблемы с отображением шрифтов на Windows. Их рендеринг сильно отставал от варианта Mac OS, а поскольку мы использовали довольно тонкий Roboto Light, мы не могли себе позволить оставлять их в таком виде. Было принято решение использовать дополнительный css-файл, в котором начертания шрифтов подменялись бы на их аналоги пожирнее. К тому же почти сразу пришло замечание от дизайнеров о том, что некоторые начертания все-таки смотрятся хорошо, и в текущем варианте их надо оставить такими же.
Благодаря конфигу нам было достаточно создать дополнительный файл stylus, в котором мы проходили по конфигу и подменяли все начертания на нужные с учетом дизайнерских условий. После компиляции мы получили 2 отдельных css-файла: один для пользователей Windows и второй для всех остальных операционных систем.
Так как проекты стали использовать много одинаковых блоков, мы должны были предоставить пользователям какой-либо дополнительный фактор для отличия наших проектов друг от друга. Этой цели мы достигли за счет использования акцентных цветов в дизайне. Для этого был заведен отдельный stylus-конфиг с описанием цветовой палитры, и на каждом отдельном проекте в него были записаны соответствующие значения. Для начала в него положили по 2 акцентных цвета и несколько цветов для фонов страниц. Со временем он оброс стандартными вариантами цветов, разделенных по семантике и оттенкам.
В случае если какой-либо из этих цветов использовался в блоке, он прописывался не напрямую в стили, а в отдельный конфиг блока. Это позволяло не получать лишних строк кода после сборки при изменении цвета у блока на своем проекте.
$stepBlock = {
default: {
border: #d8d8d8,
bg: #fff,
color: #000,
colorText: #000
},
states: {
active: {
bg: $color.primary.project,
border: $color.secondary.project,
color: #fff
},
done: {
border: $color.secondary.project,
bg: #fff,
color: #000
}
}
}
Помимо применения акцентных цветов к фонам, бордерам и тексту почти сразу же возникли случаи, когда нужно было перекрашивать иконки или менять их оттенок в зависимости от яркости акцентного цвета (например, если иконка лежит поверх него). Более того, иногда приходилось анимировать изменение их цвета и менять их размер.
Изначально все иконки на проектах собирались в наборы спрайтов из отдельных png-файлов Grunt-таском, но после появления таких кейсов часть из них мы решили сделать шрифтовыми. Дизайнеров мы попросили отрисовать их в svg и полученные файлы сложили в репозиторий в качестве отдельного блока. Каждый из проектов смог забрать себе необходимые иконки и собрать при помощи Grunt шрифт с иконками, который будет автоматически подключен на страницу.
При начале редизайна десктопных версий для проекта Здоровье Mail.Ru был нарисован дизайн, отличавшийся по стилю от остальной линейки, но так как по большей части структура блоков и их визульная составляющая была похожа на стандартные решения, было решено разрабатывать этот проект по той же методологии. Макеты Здоровья по большей части соответствовали канонам плоского дизайна и, соответственно, многие блоки выглядели легче, в них уже не было теней и градиентов, а значит можно было облегчить и верстку со стилями. При этом некоторые из них могли использоваться и в более сложных блоках, например, кнопки являлись составляющими пейджинга, комментариев, опросов и так далее.
Для облегчения верстки мы вынесли некоторые из элементов блоков в отдельные файлы, которые можно переписать на проекте, избавившись от лишних оберток. Лишние стили тащить на проект также не хотелось, поэтому все дефолтные стили были вынесены в модификатор _default, который можно отключить и начать использовать свой вариант. Например, следующая реализация зависимостей у блока button в ките и на отдельно взятом проекте:
({
mustDeps: [
{ block: 'icon' },
{ block: 'link' },
{ mod: 'default' }
],
shouldDeps: [
{ elems: ['inner', 'text', 'loader', 'count'] },
{ mods: ['large', 'font', 'color', 'loading', 'full', 'simple'] }
]
});
({
noDeps: [
{ mods: ['default', 'search'] },
{ elems: ['inner', 'text', 'count'] }
]
})
Помимо адаптивных шрифтов из вышеописанного пункта, предполагалось адаптировать контент под разные разрешения экрана. Решили не делать стандартную резину, а ограничиться тремя разрешениями экрана, на которых будет меняться ширина обертки, а также будут возможны изменения в отображении блоков. Для реализации адаптивной сетки был создан очередной конфиг, в котором были заданы размеры колонок, стандартные отступы для блоков, отвечающих за построение сетки, брейкпоинты для media queries. Поскольку в некоторых местах было необходимо достаточно сильно менять вид страницы, каждому из брейкпоинтов был присвоен свой ключ, которым мы стали пользоваться при написании модификаторов блоков и миксинов, связанных адаптивностью верстки.
$layout['sizes'] = {
small: {
media: {
max: 1279px
},
cols: 1..47,
wrapper: 940px
},
medium: {
media: {
min: 1280px,
max: 1339px
},
cols: 1..59,
wrapper: 1180px
},
large: {
media: {
min: 1340px
},
cols: 1..65,
wrapper: 1300px
}
}
mqForScreen($size, $sizes=$layout)
$mq = screen
for k in keys($sizes.sizes[$size].media)
$mq = s('%s and (%s-width: %s)', $mq, s(k), $sizes.sizes[$size].media[k])
return $mq
forScreen($size, $sizes=$layout)
$mq = mqForScreen($size, $sizes)
@media $mq
{block}
if $size == $layout.default
.no-mq &
{block}
Для всех колонок мы добавили модификаторы, задающие их размеры на каждом из разрешений. Например, любой колонке можно передать модификаторы small_10 medium_20 large_percent-50, и она, соответственно, будет занимать 10 ячеек в ширину на маленьком разрешении экрана, 20 на среднем и будет тянуться на половину родительского блока на большом. При этом все возможные размеры изначально прописаны в том же конфиге и разворачиваются в css при помощи миксинов. Аналогичным способом добавляется адаптивность остальным блокам при необходимости, а также некоторым хелпер-классам наподобие .hidden, прячущим блоки на необходимых разрешениях.
За прошедшие два года с начала работы над китовым дизайном мы добились хороших результатов, запустив мобильные версии практически для всех наших проектов и обновив некоторые из них под новый корпоративный стиль. Этот же подход уже вовсю используется и на десктоп версиях проектов: был произведен редизайн 6 проектов, и мы продолжаем активно работать в этом направлении. Сама методика уже не один раз помогла реализовать довольно сложные компоненты и существенно снизить трудозатраты по имплементации их на всех проектах.
Будем рады, если поделитесь в комментариях своим опытом разработки проектов, построенных на UI-kit, расскажете о проблемах, с которыми сталкивались.