Хабрахабр

[Перевод] Интересные CSS-находки в новом дизайне Facebook

Я любопытен. Мне всегда интересно открывать инструменты разработчика браузера и разбираться с тем, как сделан какой-нибудь сайт, на который я заглянул. Этот материал представляет собой мой первый рассказ о таких вот изысканиях. Дело в том, что я обнаружил некоторые интересные примеры использования CSS (по крайней мере, они показались интересными мне), о которых мне захотелось рассказать.

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

Использование SVG-графики в аватарах

Аватары

SVG-графика используется для изображений-аватаров, таких, как фото профиля, фото на страницах пользователя или в группах.

Вот HTML-код:

<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>

Когда я это увидел, я задался вопросом о том, почему используется именно такой подход. У меня на этот вопрос есть несколько ответов:

  • У аватара должна быть внутренняя рамка, имеющая полупрозрачный чёрный цвет (10%). Это нужно для того чтобы светлые аватары выглядели бы как круглые изображения. Причём, даже в том случае, если это — полностью белые изображения.
  • К HTML-элементу <img> нельзя добавить внутреннюю тень (box-shadow) с использованием ключевого слова inset. SVG используется для решения этой задачи.
  • Для того чтобы изображение имело бы круглую форму, используются SVG-элементы <mask> и <image>.

Как я уже сказал, рамки внутри аватара весьма полезны для светлых изображений. Вот макет, который иллюстрирует эту идею.

Аватары

Внутренняя рамка настроена с помощью следующего CSS-кода:

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>

Вот стили к этому HTML-коду:

.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>, а не внешних отступов

Я не застал тех времён, когда в качестве разделителей элементов веб-страниц использовались GIF-изображения. Но тут я увидел кое-что, напоминающее эту технику. Я так думаю, то, о чём я говорю, можно назвать div-разделителями.

Элементы <div>, используемые в роли разделителей

Позвольте мне немного пояснить вышеприведённый рисунок. Это — фрагмент раздела, в котором находятся запросы на добавление в друзья, появляющиеся на домашней странице. Перед нами — некая сетка со сведениями о людях. У этой сетки должен быть левый внешний отступ. Я обычно добавляю к элементам подобные границы так: margin-left: 16px. Но в Facebook то же самое сделали, воспользовавшись элементом <div>, который отделяет сетку от края элемента-контейнера.

Зачем дизайнеры поступили именно так? У меня есть несколько догадок:

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

Почему тут не используется внешний отступ? Как по мне, так CSS сайта (около 100 тысяч строк), который полон вспомогательных классов, способен вместить ещё один класс, позволяющий настроить отступ у нужного элемента.

Использование CSS-фильтров

Иконки, созданные с помощью элементов <img> и <svg>

Взгляните на эти четыре иконки. Иконка со значком «плюс» и иконка со стрелкой созданы с использованием элемента <img>. А иконки мессенджера и уведомления — с использованием 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.com. Мне этот код кажется очень странным. Разве сложно заменить этот элемент <img> на SVG-изображение и просто поменять цвет, задаваемый атрибутом fill?

То же самое используется и при создании значка для верифицированных аккаунтов.

Значок верифицированного аккаунта

Нечто подобное применяется и для ссылок из профиля пользователя:

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

Вот как это выглядит

Ссылки в профиле пользователя

Если вам интересно узнать о том, как с помощью CSS-фильтров окрасить в любой цвет изображение, выполненное чёрным цветом, — взгляните на этот ответ на Stack Overflow. Там же можно найти инструмент для создания CSS-фильтров. Кроме того — взгляните на этот твит.

Использование изображений для создания теней

Тень, созданная с помощью background-image

У главного заголовка окна имеется тень. Можно предположить, что она создана с использованием CSS-свойства box-shadow. Но на самом деле это не так. Тут использован элемент <div> с фоновым изображением, которое повторяется по оси x.

Я загрузил использованное здесь изображение, что позволяет рассмотреть его поближе.

Изображение, используемое для создания тени

Это — изображение размером 2x14 пикселей, которое, для создания эффекта тени, многократно повторяется. В формировании эффекта тени участвует не только изображение, но и специальный элемент <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;}

Вот видео, в котором демонстрируется то, что происходит после переключения на тёмную тему. Советую смотреть его в полноэкранном режиме.

Использование CSS-свойства line-clamp для обрезки многострочного текста

В подписях используется обрезка многострочного текста

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

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

Это — встроенные стили, которые, кроме того, меняются в зависимости от браузера.

Код обрезки текста в Chrome и Firefox

Эта возможность CSS пользуется довольно хорошей браузерной поддержкой. CanIUse сообщает о том, что это свойство поддерживают (хотя и с префиксом) все ведущие браузеры. Подробности об этом свойстве можно почитать здесь.

Использование div для создания элементов, реагирующих на наведение на них указателя мыши

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

.element:hover { background: #ccc;}

Однако возникает такое ощущение, что на больших сайтах, вроде сайта Facebook, такой подход непрактичен. В процессе исследования сайта я обратил внимание на элемент, который выводится только при наведении на него указателя мыши (назовём его «hover-элемент»). В этом заключается его основная задача. Вот его стиль:

.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;}

Значение свойства opacity этого элемента меняется средствами JavaScript с 0 на 1. Я поэкспериментировал с ним и выяснил, что он используется для множества компонентов. Ниже приведён набор скриншотов, демонстрирующих использование этого элемента.

Использование hover-элемента

Мне нравится единообразие и простота, обеспечиваемые использованием одного и того же эффекта для множества элементов. Если это что-то и значит, то значит это то, что используемый на сайте язык дизайна единообразен, и то, что система была спроектирована аккуратно. Хорошая работа, Facebook!

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

Речь идёт о сокращённой записи значений свойства, подразумевающей воздействие на верхнюю, правую, нижнюю, и левую часть элемента. Пользоваться свойством inset можно так:

.element { inset: 4px 0; /* Это эквивалентно следующему: top: 4px, bottom: 4px, left: 0, right: 0 */}

Свойство inset настраивается для вышеописанных hover-элементов, связанных с некоторыми другими элементами. Стиль встроен в HTML. Я заметил его применение к компоненту, показанному ниже.

Синим цветом выделены области, на которые воздействует свойство inset

Во время написания этого материала свойство inset поддерживает лишь Firefox 66+.

Атрибут dir=«auto» и логические CSS-свойства 

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

Более того, надо отметить, что тут имеется встроенный стиль, который меняет направление текста (возникает такое ощущение, что атрибута dir=«auto» недостаточно). Вот как то выглядит:

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

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

Если вас интересуют особенности RTL-стилизации — взгляните на этот мой материал.

Динамический фон, зависящий от главной фотографии

Фон, зависящий от главной фотографии

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

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

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

Нахождение доминантного цвета

▍2. Добавление фона, использующего доминантный цвет

Фон, использующий найденный доминантный цвет

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

▍3. Добавление градиента поверх фона

Для добавления градиента поверх фона используется такой CSS-код:

.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));}

Добавление градиента (в дизайне используются тёмные цвета)

Для нахождения доминантного цвета изображения можете воспользоваться этим инструментом.

Множественные тени

Тени

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

Выпадающие меню

Вот CSS-код:

.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-тень белого цвета с прозрачностью в 50%? Дело в том, что это — тень для тёмного режима. Ниже показан увеличенный фрагмент интерфейса, в котором используется такая тень.

Увеличенный фрагмент интерфейса с inset-тенью

Мне нравится это разумное решение.

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

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

Пустые элементы в сеточном макете

Вот CSS-код:

.wrapper { display: flex; flex-wrap: wrap; justify-items: space-between;} .item { width: 205px;}

Выглядит всё это интересно, правда? Использование значения space-between при настройке размещения элементов — дело рискованное. Макет будет выглядеть неправильно в том случае, если, например, имеется всего три фотографии. Ниже показан пример такого макета.

Опасность использования space-between

Как команда Facebook справилась с этой проблемой? Очень просто: тут имеются четыре пустых элемента <div>, ширина которых равна ширине фото. Вот HTML-код этого решения:

<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>

При таком подходе подобные пустые элементы <div> играют роль искусственных элементов. Они помогают поддерживать одинаковое расстояние между элементами.

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

Я редко вижу вертикальные медиа-запросы, так сказать, в диком виде. Мне нравится то, что разработчики из Facebook использовали такой запрос для уменьшения ширины ленты новостей на домашней странице:

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

Итоги

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

Уважаемые читатели! А вы находили когда-нибудь что-нибудь интересное, анализируя код сайтов, которые посещаете?

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»