Летом 2011 года мы внедрили новый дизайн Почты Mail.Ru. Интерфейс изменился не только визуально, но и был полностью переделан в техническом плане, что в разы ускорило его быстродействие и удобство. Но еще это было необходимо для реализации очень желанной для пользователей фичи — тем оформления интерфейса. О том, как мы внедряли темы в Почте, хочу рассказать в этом посте.

Буквально за неделю до этого мы закончили перевод основных страниц на БЭМ (http://bem.github.com/bem-method/pages/beginning/beginning.ru.html). Решено было делать тематическую кастомизацию почты только на CSS. Но чистого CSS мало, нужен инструмент генерации CSS – и мы выбрали SASS (http://sass-lang.com/).
Как я писал выше, к тому моменту как мы начали делать темы на Почте, основные страницы у нас уже были переведены на независимые блоки.
Плюс, который мы получили сразу, – отсутствие каскадируемости. Если перекрашиваешь блок, ты перекрашиваешь только этот блок, ничего и нигде не стреляет и не взрывается.
Тема – это картинки на фоне, а также цвета (шрифт, фон, бордер...). Мы брали блок за блоком и выносили всю цветовую часть отдельно.
Естественно, нам понадобилась система сборки. SASS подошел. Он позволяет собирать scss в один большой css-файл плюс дает возможность использовать переменные.
Стилевое оформление одного из блоков строится так:
В этих стилевых файлах все значения css-свойств – переменные, которые определяются в конкретной теме.
После работы SASS мы получаем файл с геометрией всех блоков:
и файлы с цветовым оформлением, разбитые по темам:
Пользователю загружаются два файла – геометрия и стили выбранной темы.
Переключение темы
Т.к. оформление отделено от геометрии, нет необходимости перезагружать страницу для изменения оформления. Все, что нужно – загрузить асинхронно стили новой темы, после чего удалить старые.
Для работы с темами создан JS-класс, основными интересующими нас методами которого являются setTheme и switchThemeCss.
Система обратных вызовов для асинхронной работы с темами построена с помощью deferred.
Метод setTheme – “точка входа” для установки темы. Принимает id темы и возвращает deferred, который ожидает выполнения двух методов: switchThemeCss (смена стилей темы оформления) и AJAX-запрос на сохранение выбранной темы. Оба эти метода тоже работают с deferred.
Самое интересное происходит внутри switchThemeCss. Основываясь на переданном themeId, функция строит путь до файла стилей и пытается его загрузить.
В проекте к этому моменту уже использовался плагин для загрузки стилей jquery.getCSS, и мы решили им воспользоваться. Но, к сожалению, он не позволяет определить, действительно ли загрузились стили, или запрос “отвалился” по таймауту из-за отсутствия подключения к сети, что очень важно для нас, т.к. удалять стили старой темы нужно только после успешного применения стилей новой.
Эту проблему мы решили с помощью многим, вероятно, известного, но успешно забытого метода, заключающегося в следующем.
На страницу добавляется скрытый блок, на который “навешиваются” стили, уникальные для каждой темы. После загрузки файла стилей проверяем, изменились ли стили скрытого блока, и если изменились – значит, тема успешно загружена.
В нашем случае 100% уникальность имеет id темы. Но этот id не имеет ничего общего с возможными значениями CSS-свойств, а значит, в общем случае стили применены не будут.
Нужно было некоторое свойство, значением которого может быть произвольная строка. Для этого подходят свойства content и font-family, но с некоторыми оговорками.
Одни браузеры не применяют свойство content к не псевдо-элементам, для которых оно предназначено. А другие не применяют свойство font-family, если такого семейства в реальности не существует, но при этом применяют свойство content. Мы решили использовать оба свойства, и в браузерах, которые возвращают пустое значение свойства content, смотреть в свойство font-family.
В итоге получился примерно следующий код (схематично):
В итоге мы получили темы, которые легко поддерживать. Кроме того, их загрузкой легко управлять, а код загрузки можно использовать с любым интерфейсным решением для выбора тем на любой странице.
На техническую реализацию, создание 12 тем, отладку и выпуск на продакшн ушло 2 месяца работы одного человека.
Напоследок — забавная статистика о самых популярных темах:
Зомби устанавливают в основном мальчики 12-25. А еще это любимая темы команды Почты Mail.ru:)

Игра в снежки удерживает лидерство в сегменте тем про зиму, на данный момент ее установили уже более 300 000 человек, 2/3 из которых представительницы прекрасного пола 25-34

Самая девчачья тема Анимэ (девочки 12-17 лет)

Самая гиковская тема — Blackboard. Включить ее из интерфейса вообще нельзя! Зато можно включить вот по такой читерской ссылке http://e.mail.ru/cgi-bin/msglist?folder=0&setTheme=t1026
Саймонс Кэт — темы для молодых женщин 25-34

P.S: Приоткрывая завесу тайны — в ближайшее время выйдут первые две темы для фанатов игр Аллоды и Легенда.
Андрей Сумин,
руководитель разработки клиентской части

Буквально за неделю до этого мы закончили перевод основных страниц на БЭМ (http://bem.github.com/bem-method/pages/beginning/beginning.ru.html). Решено было делать тематическую кастомизацию почты только на CSS. Но чистого CSS мало, нужен инструмент генерации CSS – и мы выбрали SASS (http://sass-lang.com/).
Как я писал выше, к тому моменту как мы начали делать темы на Почте, основные страницы у нас уже были переведены на независимые блоки.
/css
/blocks
/messages
...
/pages
main.css
Плюс, который мы получили сразу, – отсутствие каскадируемости. Если перекрашиваешь блок, ты перекрашиваешь только этот блок, ничего и нигде не стреляет и не взрывается.
Тема – это картинки на фоне, а также цвета (шрифт, фон, бордер...). Мы брали блок за блоком и выносили всю цветовую часть отдельно.
/css
/blocks
/messages // геометрия блоков
/themes
/default
/messages // файлы с переменными оформления
default.scss // сборка всех блоков с переменными
default.vars.scss // значения переменных по умолчанию
/theme
theme.scss // файл с нужными инклудами
theme.vars.scss // “окрас” конкретной темы
/pages
…
Естественно, нам понадобилась система сборки. SASS подошел. Он позволяет собирать scss в один большой css-файл плюс дает возможность использовать переменные.
Стилевое оформление одного из блоков строится так:
/css/blocks/messages/messages.scss
.messages{
padding:20px;
}
/css/pages/mail.scss
@import url(../messages/messages.scss);
/css/themes/default/messages/messages.scss
.messages{
background:$messages-background;
}
/css/blocks/default/default.scss
@import url(messages/messages.scss);
В этих стилевых файлах все значения css-свойств – переменные, которые определяются в конкретной теме.
/css/themes/theme/theme.vars.scss
$messages-background: #FFF;
/css/themes/theme/theme.scss
@import url(theme.vars.scss);
@import url(../default/default.scss);
После работы SASS мы получаем файл с геометрией всех блоков:
/css/pages/mail.css
и файлы с цветовым оформлением, разбитые по темам:
/css/themes/theme/theme.css;
Пользователю загружаются два файла – геометрия и стили выбранной темы.
Переключение темы
Т.к. оформление отделено от геометрии, нет необходимости перезагружать страницу для изменения оформления. Все, что нужно – загрузить асинхронно стили новой темы, после чего удалить старые.
Для работы с темами создан JS-класс, основными интересующими нас методами которого являются setTheme и switchThemeCss.
Система обратных вызовов для асинхронной работы с темами построена с помощью deferred.
Метод setTheme – “точка входа” для установки темы. Принимает id темы и возвращает deferred, который ожидает выполнения двух методов: switchThemeCss (смена стилей темы оформления) и AJAX-запрос на сохранение выбранной темы. Оба эти метода тоже работают с deferred.
return $.when(AJAX.post(...), switchThemeCss(themeId));
Самое интересное происходит внутри switchThemeCss. Основываясь на переданном themeId, функция строит путь до файла стилей и пытается его загрузить.
В проекте к этому моменту уже использовался плагин для загрузки стилей jquery.getCSS, и мы решили им воспользоваться. Но, к сожалению, он не позволяет определить, действительно ли загрузились стили, или запрос “отвалился” по таймауту из-за отсутствия подключения к сети, что очень важно для нас, т.к. удалять стили старой темы нужно только после успешного применения стилей новой.
Эту проблему мы решили с помощью многим, вероятно, известного, но успешно забытого метода, заключающегося в следующем.
На страницу добавляется скрытый блок, на который “навешиваются” стили, уникальные для каждой темы. После загрузки файла стилей проверяем, изменились ли стили скрытого блока, и если изменились – значит, тема успешно загружена.
В нашем случае 100% уникальность имеет id темы. Но этот id не имеет ничего общего с возможными значениями CSS-свойств, а значит, в общем случае стили применены не будут.
Нужно было некоторое свойство, значением которого может быть произвольная строка. Для этого подходят свойства content и font-family, но с некоторыми оговорками.
Одни браузеры не применяют свойство content к не псевдо-элементам, для которых оно предназначено. А другие не применяют свойство font-family, если такого семейства в реальности не существует, но при этом применяют свойство content. Мы решили использовать оба свойства, и в браузерах, которые возвращают пустое значение свойства content, смотреть в свойство font-family.
В итоге получился примерно следующий код (схематично):
function getApplyedThemeId(){
var ff = hiddenElement.css('font-family'),
content = hiddenElement.css('content');
return content || ff;
},
function switchThemeCss (themeId){
var url = getThemeCssUrl(themeId)
, deferred = newDeferred();
var oldThemeId = getApplyedThemeId(); // Получаем текущий themeId
$.getCSS(
url,
function(link){
var newThemeId =getApplyedThemeId(); // Получаем новый themeId
// Если id не совпадают – тема загрузилась
if (newThemeId !== oldThemeId){
$ThemeCSS.remove(); // Удаляем старые стили
$ThemeCSS = $(link); // Запоминаем новые
deferred.resolve(); // Возвращаем ОК
} else { // Тема по какой-то причине не загрузилась
$(link).remove(); // Удаляем link на неудачные стили
deferred.reject(); // Возвращаем “все плохо”
}
}
);
}
В итоге мы получили темы, которые легко поддерживать. Кроме того, их загрузкой легко управлять, а код загрузки можно использовать с любым интерфейсным решением для выбора тем на любой странице.
На техническую реализацию, создание 12 тем, отладку и выпуск на продакшн ушло 2 месяца работы одного человека.
Напоследок — забавная статистика о самых популярных темах:
Зомби устанавливают в основном мальчики 12-25. А еще это любимая темы команды Почты Mail.ru:)

Игра в снежки удерживает лидерство в сегменте тем про зиму, на данный момент ее установили уже более 300 000 человек, 2/3 из которых представительницы прекрасного пола 25-34

Самая девчачья тема Анимэ (девочки 12-17 лет)

Самая гиковская тема — Blackboard. Включить ее из интерфейса вообще нельзя! Зато можно включить вот по такой читерской ссылке http://e.mail.ru/cgi-bin/msglist?folder=0&setTheme=t1026
Саймонс Кэт — темы для молодых женщин 25-34

P.S: Приоткрывая завесу тайны — в ближайшее время выйдут первые две темы для фанатов игр Аллоды и Легенда.
Андрей Сумин,
руководитель разработки клиентской части