Обложка статьи «Разбираем CSS в новом дизайне Facebook: легаси, неочевидные решения и ответы разработчиков»

Разбираем CSS в новом дизайне Facebook: легаси, неочевидные решения и ответы разработчиков

Перевод статьи «CSS Findings From The New Facebook Design»

Автор перевода Мария Багулина

Недавно Facebook обновил дизайн. Ahmad Shadeed, UX/UI-дизайнер и фронтенд-разработчик, разобрал CSS-файл сайта и поделился своими находками. При этом некоторые решения команды разработчиков FB оказались для него неочевидны, и он спросил их напрямую.

Аватары формата SVG

 

Аватары формата SVG
Теперь для фотографий профиля, которые отображаются на вашей странице или страницах групп, используется формат SVG.

<svg role="none" style="height: 36px; width: 36px;">
  <mask id="avatar">
    <circle cx="18" cy="18" fill="white" r="18"></circle>
  </mask>
  <g mask="url(#avatar)">
    <image x="0" y="0" height="100%" preserveAspectRatio="xMidYMid slice" width="100%" xlink:href="avatar.jpg" style="height: 36px; width: 36px;"></image>
    <circle cx="18" cy="18" r="18"></circle>
  </g>
</svg>

Почему именно SVG? У меня есть несколько мыслей на этот счёт:

  • Аватар должен иметь внутреннюю границу чёрного цвета с прозрачностью 10%, чтобы иллюстрация выглядела круглой, даже если у неё полностью белый фон.
  • Свойство box-shadow нельзя добавить к HTML-тегу <img>, поэтому используется SVG.
  • Чтобы сделать изображение круглым, используются SVG-элементы <mask> и <image>.

Граница внутри аватара помогает красиво отображать яркие или полностью белые изображения. Это хорошо видно на макете:

Отображение ярких или полностью белых изображений
Внутренняя граница добавляется так:

circle,
rect {
  stroke-width: 2;
  stroke: rgba(0, 0, 0, 0.1);
  fill: none;
}

И если изображение прямоугольное, используется форма rect:

<svg role="none" style="height: 36px; width: 36px;">
  <mask id="avatar">
    <rect cy="18" fill="white" height="36" rx="8" ry="8" width="36" x="0" y="0"></rect>
  </mask>
  <g mask="url(#avatar)">
    <image x="0" y="0" height="100%" preserveAspectRatio="xMidYMid slice" width="100%" xlink:href="avatar.jpg" style="height: 36px; width: 36px;"></image>
    <rect cy="18" fill="white" height="36" rx="8" ry="8" width="36" x="0" y="0"></rect>
  </g>
</svg>

Интересно, что аватары в фиде домашней страницы создаются с помощью тега <img> и элемента <div> для внутренней границы.

<div class="avatar-wrapper>
    <img class="avatar" width="40" height="40" src="avatar.jpg" width="40" alt="">
    <div class="avatar-outline"></div>
</div>
.avatar-wrapper {
    position: relative;
}

.avatar {
    display: block;
    border-radius: 50%;
}

.avatar-outline {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
    border-radius: 50%;
}

Поскольку SVG применяется только в нескольких местах, я полагаю, что причина использования <img> и <div> связана с размером страницы. Если бы SVG присутствовал в ленте новостей, то при прокрутке страницы число загружаемых килобайт сильно возрастало.

Div-заглушки вместо отступов


В фиде главной страницы случайным образом отображается раздел запросов в друзья. Он выглядит как таблица аватаров с небольшим полем слева, которое задаётся элементом <div>.

Я бы добавил это поле с помощью margin-left: 16px — насколько я видел, в CSS (~100 тыс. строк) множество utility-классов, и можно было бы легко добавить класс в обёртку, чтобы сделать отступ. Тем не менее, Facebook добавил элемент <div>, чтобы заполнить пространство у края контейнера.

Я задумался о причине такого решения.

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

CSS-фильтры

Наверху показаны четыре иконки. Плюс и стрелка — это обычные изображения, а иконки мессенджера и уведомлений — элементы SVG. В чём причина такого смешения?

При нажатии на последнюю иконку значок становится синим. Для изменения цвета используется CSS-фильтр.

.icon {
    filter: invert(39%) sepia(57%) saturate(200%) saturate(200%) saturate(200%) saturate(200%) saturate(200%) saturate(147.75%) hue-rotate(202deg) brightness(97%) contrast(96%)
}

Да, это продакшн-код Facebook, и он выглядит очень странно. Почему нельзя использовать SVG и просто поменять цвет через fill?

Это же решение используется и для галочки подтверждённой страницы.


А также для ссылок в профиле пользователя.

.icon {
    filter: invert(59%) sepia(11%) saturate(200%) saturate(135%) hue-rotate(176deg) brightness(96%) contrast(94%);
}


Технический менеджер React-команды Facebook ответил мне в Twitter, что значки <img> с фильтрами CSS — это временная магия, чтобы легаси-иконки работали во всех цветах и в тёмной теме, пока команда не преобразует их в SVG в React.

Если вам интересно узнать больше о том, как преобразовать чёрное изображение в любой другой цвет с помощью фильтров CSS, вот неплохое решение. Кроме того, есть инструмент Барретта Соннтага для этих целей.

Тень в виде изображения


У основного header есть тень, и можно предположить, что она добавляется через CSS box-shadow, но нет. Facebook использует фоновое изображение, которое повторяется по оси x.

Я скачал это изображение, чтобы рассмотреть его поближе.


Это повторяющаяся картинка размером 2×14 пикселей, которая находится в отдельном <div>. Какой смысл её использовать?

Фронтенд-разработчик Facebook ответил, что причина кроется в производительности. Оказывается, крошечная тень может вызвать много проблем — например разработчики обнаружили, что при скролле страницы с множеством видео рандомные части страницы могут исчезать и появляться снова.

Экстенсивное использование CSS-переменных

Мне понравилось, что Facebook использует переменные CSS. Из того, что я увидел, в элемент :root добавлено более 320 переменных, в том числе для тёмной темы.

Когда включается тёмная тема, к HTML-элементу добавляется класс __fb-dark-mode. Он переопределяет все переменные в :root следующим образом:

:root {
    /* Переменные светлой темы */
    -fds-active-icon:  #3578E5;
    --fds-attachment-footer-background:  #F2F3F5;
    --fds-blue-05:  #ECF3FF;
    --fds-blue-30:  #AAC9FF;
    --fds-blue-40:  #77A7FF;
}

.__fb-dark-mode:root, .__fb-dark-mode {
    /* Переопределённые переменные */
    --fds-active-icon:  black;
    --fds-attachment-footer-background:  black;
    --fds-blue-05:  black;
    --fds-blue-30:  black;
    --fds-blue-40:  black;
}

Вот видео, где можно увидеть переключение класса (рекомендую развернуть на весь экран).

Усечение многострочного текста


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

.element {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

Указанные выше свойства добавляются как inline-стили и меняются в зависимости от браузера:


Поддержка браузера — хорошее решение. Согласно CanIUse, все основные браузеры поддерживают это свойство, но с префиксом. Подробнее об усечении текста вы можете узнать в этой статье.

Div для эффектов наведения

Обычно эффекты при наведении создаются с помощью CSS. Например, если нужно поменять оттенок серого при наведении на кнопку, можно сделать так:

.element:hover {
    background: #ccc;
}

Но, похоже, что для такого большого сайта как Facebook это не практично. Я заметил элемент, который отображается только при наведении и ведёт себя как hover-элемент CSS.

.hover-div {
    position: absolute;
    right: 0;
    left: 0;
    top: 0;
    bottom: 0;
    pointer-events: none;
    border-radius: 6px;
    inset: 4px 0px;
    background-color: var(--hover-overlay);
    transition-property: opacity;
    transition-timing-function: var(--fds-animation-fade-out);
    cursor: pointer;
}

Этот элемент переключается в JavaScript, чтобы изменить значение opacity с 0 на 1, поэтому я немного поиграл с ним и обнаружил, что он используется для множества компонентов. Посмотрите на скриншоты ниже:


Мне нравится последовательность и простота такого эффекта. Похоже, дизайн-система Facebook спроектирована очень тщательно.

Использование свойства inset

Это сокращение для свойств top, right, bottom и left. Его можно использовать так:

.element {
    inset: 4px 0;
    /* Что эквивалентно: top: 4px, bottom: 4px, left: 0, right: 0 */
}

Свойство inset используется в div для эффектов наведения у некоторых элементов, например:


На момент написания статьи inset поддерживается только в Firefox 66+ в соответствии с CanIUse.

Dir=Auto и логические свойства CSS

Для многоязычного веб-сайта, такого как Facebook, иногда трудно предсказать, как будет выглядеть содержимое. Например, имя пользователя в компоненте post имеет атрибут dir="auto". Это означает, что направление текста будет зависеть от языка — например, слева направо для английского и справа налево для арабского.

Кроме того, есть inline-стиль CSS, который меняет направление текста (похоже, dir="auto" может быть недостаточно).

<div dir="auto" style="text-align: start;">محتوى بالعربية</div>

Обратите внимание, что здесь используется text-align: start. Это логическое свойство CSS, эквивалентное left-to-right определителю text-align: right.

Динамический фон обложки


Вы заметили, что на странице есть градиент, схожий с основным цветом фотографии обложки? Он добавляется динамически в зависимости от цвета иллюстрации. Как это работает?

1. Получение доминирующего цвета

Для начала нужно получить доминирующий цвет фотографии. После этого создаётся небольшое изображение этого цвета.


2. Добавление фона доминирующего цвета


Цвет фона становится доминирующим. Белой рамкой показана исходная фотография обложки.

3. Добавление градиента

.element {
    background-image: linear-gradient(to top, #FFFFFF, rgb(255, 255, 255), rgba(255,255,255,0.7), rgba(255,255,255,0.4), rgba(255,255,255,0));
}


А когда включена тёмная тема, градиент уходит в чёрный, а не в белый:

.element {
    background-image: linear-gradient(to top, #000, rgb(0, 0, 0), rgba(0,0,0,0.7), rgba(0,0,0,0.4), rgba(0,0,0,0));
}

Вы можете использовать этот инструмент для извлечения доминирующего цвета изображения.

Глубокие тени


Мне понравилось, как команда реализовала тени для некоторых элементов, например для выпадающего меню. У них появилась глубина, которая выглядит намного реальнее, чем то, что дают обычные инструменты для теней.

.element {
    box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(255, 255, 255, 0.5);
}

Вы можете задаться вопросом, почему здесь присутствует inset-тень белого цвета? Она используется для тёмной темы:


Пустые элементы для flexbox-сеток

Все сетки на сайте используют flexbox. Одна из них, в разделе «Ваши фотографии», привлекла моё внимание.

.wrapper {
    display: flex;
    flex-wrap: wrap;
    justify-items: space-between;
}

.item {
    width: 205px;
}

Использовать space-between для пробелов между элементами довольно рискованно — это может плохо выглядеть, если будет всего три изображения. Примерно так:


Чтобы решить эту проблему, в Facebook добавили четыре пустых <div> с одинаковой шириной для каждой фотографии.

<div class="wrapper">
    <div class="item"><a href="#"><img src="photo.jpg"></a></div>
    <div class="item"><a href="#"><img src="photo.jpg"></a></div>
    <div class="item"><a href="#"><img src="photo.jpg"></a></div>
    <div class="item"><a href="#"><img src="photo.jpg"></a></div>
    <div class="empty"></div>
    <div class="empty"></div>
    <div class="empty"></div>
    <div class="empty"></div>
</div>

Эти пустые элементы обеспечивают одинаковое расстояние между изображениями.

Вертикальные медиа-запросы

Я редко встречал вертикальные медиа-запросы в работающих сайтах. Но Facebook использовал один такой элемент, чтобы сократить ширину ленты новостей на главной странице.

@media (min-height: 700px) {
    .element {
        width: 584px;
    }
}

The end

Разбираясь в CSS больших проектов с огромным количеством пользователей, можно почерпнуть много интересного. Надеемся, эта статья была полезна для вас. Как вам новый дизайн Facebook? Поделитесь в комментариях.

Вакансии в тему: