[Перевод] Искусство компонентов. Пишем карточку контакта Facebook Messenger

Вполне возможно оценить компонент и сказать, что он легко пишется на HTML и CSS. Соглашусь, это легко, когда вы работаете, только чтобы практиковаться, но в реальном проекте всё по-другому. Идеальный адаптивный компонент, который вы только что создали, быстро перестаёт работать, когда сталкивается с реальным контентом настоящего проекта. Почему? Потому, что, пока вы рассуждаете о разработке компонента, вы можете упустить крайние случаи. Я покажу простой на первый взгляд компонент, за которым стоит огромная работа. Ради реалистичности это будет пример прямо из Facebook Messenger.
vwuq7_x3nzmpqsu4rxn6boagxdg.jpeg


Начнем


Я беру очень простой компонент Facebook Messenger, посмотрите на скриншот ниже:
lxqjyyzxefqp50onyemtfqiue9a.png

В этом сайдбаре списком карточек перечисляются люди, которым я писал на Facebook. Здесь меня интересует только карточка. Как вы напишете её на HTML/CSS? Да очень легко, правда? Есть соблазн сказать, что это всего лишь картинка и слой рядом с ней. Вот о чём можно подумать:

jyv6fcnrmtszfarm1bd3i2yvbda.png

Случай выше имеет место, если вы только учитесь или соревнуетесь на UI-челлендже, но когда хочется написать надёжную карточку, которая растягивается, то пример выше быстро перестаёт работать. Посмотрим на простой вариант:

Ahmad Shadeed

You: Thanks, sounds good! . 8hr

.card {
  position: relative;
  display: flex; /* [1] */
  align-items: center; /* [2] */
  background-color: #fff;
  padding: 8px;
  border-radius: 7px;
  box-shadow: 0 3px 15px 0 rgba(0, 0, 0, 0.05);
}

.card h3 {
  font-size: 15px;
}

.card p {
  font-size: 13px;
  color: #65676b;
}

.card__image {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  margin-right: 12px;
}

.card__seen {
  position: absolute; /* [3] */
  right: 16px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  border-radius: 50%;
}

Я выделил несколько строчек, их я хочу объяснить:
  1. Использовался flexbox, потому что у нас горизонтальный дизайн, а flexbox хорошо подходит для него.
  2. Дочерние элементы нужно центрировать вертикально.
  3. Значок позиционирован абсолютно, также он центрируется вертикально.

Ломаем компонент


Здесь нет ничего плохого, но компонент не масштабируется, так что покажу другой вариант:
0whhhjvp0vfclrlralsb9sh5fa8.png

Синий значок справа означает, что пришло новое сообщение, которое я ещё не открывал. Зелёный цвет на аватаре показывает, что пользователь сейчас в сети.

Обратите внимание: у нас есть два новых значка. Как лучше добавить их на карточку? Если вы обратитесь к CSS, который я написал для самого первого компонента, то увидите, что там есть класс .card_seen для маленьких аваторов пользователей справа. В этом варианте .card_seen должен быть заменён синим значком. С уже написанными HTML и CSS, не изменив HTML, написать такое невозможно.

Для ясности уточню, что вариант, который я показываю, — только поверхность возможностей. У компонента много вариаций и случаев использования.

Все вариации


Ниже я показываю все вариации компонента. Очень старался описать их все (да, я нарисовал всё это вручную).
sb6tejuiit2-ftjqlqrfqzugaue.png

Но и этого мало: мы должны учитывать стили тёмной темы.

2hkyy5hj2uug6jdmh6rrr-a9k-q.png

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

Интервалы


Прежде всего я внимательно изучил интервалы каждого элемента UI, работая с рисунками ниже. Видите, как много интервалов и размеров в этом простом компоненте.
c31awgc4ybov46siw_zvxi7r6uk.png

В разработке и реализации UI на HTML и CSS одна из ключевых вещей, которой нужно уделить внимание, — это интервалы. Если интервалы недооценить, позже, возможно, придётся изменить сам UI.

Области компонента


Чтобы написать приличный компонент, нужно сначала тщательно продумать разметку. У нас есть две области компонента с несколькими вариациями: аватаром и областью контента.

Аватар


x0wuysa0upjkaqqvbcn1ccdyefy.png

Чтобы работать над HTML аватара, сначала нужно разобраться с его состояниями. Вот возможные варианты:

  • Один аватар.
  • Один аватар со значком онлайн-статуса.
  • Несколько аватаров для группового чата.
  • Несколько аватаров со значком онлайн-статуса.

Учитывая HTML ниже, мы хотим удостовериться, что .card__avatar работает со всеми вариантами аватаров выше.

Один аватар


Давайте увеличим масштаб HTML и сосредоточимся на первом варианте, то есть на одном аватаре. У аватара должна быть граница (или внутренняя тень), чтобы он выглядел как круг, даже если он полностью белый.
apul5-mimlkxbnn0ef8pwygkl08.png

В CSS невозможно применить внутреннюю box-shadow к элементу img. У нас есть два варианта:

  • Дополнительный div с прозрачным border.
  • Можно написать svg.

Цель внутренней границы — показать контур вокруг аватара в случаях, когда у нас:
  • Полностью белый аватар светлой темы;
  • Полностью чёрный аватар тёмной темы.

4rjzedirboabytj06d-jhoqswsa.png

Без внутренней границы полностью белый аватар смешается с родительским фоном. Это случится и с тёмной темой. Вот что отрисовывается с внутренней границей и без неё.

xxldcgz7hv3vt-rd2rj-jqufbqo.png

div для внутренней границы


В этом решении дополнительный элемент (здесь это div) абсолютно расположен над изображением с непрозрачностью 0.1.
.card__avatar {
  position: relative;
}

.card__avatar img {
  width: 56px;
  height: 56px;
  border-radius: 50%;
}

.border {
  position: absolute;
  width: 56px;
  height: 56px;
  border: 2px solid #000;
  border-radius: 50%;
  opacity: 0.1;
}

Это решение работает, но у него есть ограничения, о которых я скоро расскажу.

Работаем с svg


В этом решении воспользуемся элементом svg. Вот идея: использовать круглую маску для аватара, а для внутренней границы — элемент circle. SVG отлично подходит для этого.

  
	
  
  
	
	
  
.border {
  stroke-width: 3;
  stroke: rgba(0, 0, 0, 0.1);
  fill: none;
}

Оба решения хороши, когда строится один аватар только как пример. Всё станет интереснее, когда мы добавим элемент значка онлайн-статуса.

Единственный аватар со значком онлайн-статуса


В режиме светлой темы зелёный значок обведён белой границей. Но в тёмном режиме значок должен быть вырезан из самого аватара. Другими словами, нужно применить маску.
mv1naahbjk96jdkb8-bxdp_absm.png

Как это сделать? Оказывается, если воспользоваться SVG-решением для того самого единственного аватара, проблема легко решается при помощи маски SVG.



  
	
	
	
	
  
  
  
	
	
  

Позвольте объяснить этот код:
  1. Круг маскирует аватар.
  2. В правом нижнем углу аватара вырезается маленький кружок.
  3. Группа, которая содержит circle и image для прозрачной внутренней границы.

Вот рисунок, который объясняет, как несколько окружностей работают в качестве маски. Это какая-то магия, правда?
az7k2qqokbbwrgjnb5n7l97e_4o.png

Так выглядит HTML аватара со значком онлайн-статуса.

.card__avatar {
  position: relative;
  display: flex;
  margin-right: 12px;
}

.badge {
  position: absolute;
  right: 3px;
  bottom: 3px;
  width: 10px;
  height: 10px;
  background: #5ad539;
  border-radius: 50%;
}

Когда подобный этому компонент должен адаптироваться под разные темы, настоятельно рекомендуется хранить изменяемые цвета в переменных СSS.
:root {
  --primary-text: #050505;
  --secondary-text: #65676b;
  --bg-color: #fff;
}

html.is-dark {
  --primary-text: #e4e6eb;
  --secondary-text: #b0b3b8;
  --bg-color: #242526;
}

.card {
  background-color: var(--bg-color);
}

.card__title {
  color: var(--primary-text);
}

.card__subtitle {
  color: var(--secondary-text);
}

vczeayquyuqpmvhj-d2qvtnteho.gif

Несколько аватаров в групповом чате


В случае чата с несколькими людьми в зоне аватаров находятся два аватара, каждый будет располагаться в правом верхнем и левом нижнем углах соответственно. Чтобы выровнять один и несколько аватаров, нужно установить фиксированный размер для их родительского элемента.
.card__avatar {
  width: 56px;
  height: 56px;
}

d07y6yo-mebl2x5x9ilszlejsxw.png

Этот вариант требует изменить разметку вот так:
.card__avatar--multiple {
  position: relative;
  width: 56px;
  height: 56px;
}

.card__avatar--multiple .avatar {
  position: absolute;
}

.card__avatar--multiple .avatar-1 {
  right: 0;
  top: 0;
}

.card__avatar--multiple .avatar-2 {
  left: 0;
  bottom: 0;
}

.card__avatar--multiple .badge {
  right: 6px;
  bottom: 6px;
}

_pwng-wxxdmeoae31pwabq82lp8.png

Контент


В этой области пользователь увидит имя человека, с которым общается, а также текст сообщения или действие.
rc8f5jpcfvrv0ptdoigajhidmmw.png

Я могу представить разделённую на две части разметку, одна — для текста (имени, сообщения или действия), а вторая — для индикатора справа (новое сообщение, увиденное, приглушённое, отправленное).

xtanyx97m-u1ii7gy_ue8vaibii.png

Первая часть


Давайте внимательно посмотрим на разметку области контента.

Ahmad Shadeed

You: Thanks, sounds good. What about doing a webinar, too?

.
.card__content {
  display: flex;
  flex: 1;
}

.card__content__start {
  display: flex;
  flex: 1;
}

.card__content__start .row {
  display: flex;
  align-items: center;
}

.card__content__end {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-left: 12px;
}

.separator {
  margin-left: 4px;
  margin-right: 4px;
}

С кодом выше область содержимого выглядит, как показано ниже (это скриншот из Firefox).
m07yrh2rdtk74mrm90zxdny6_og.png

Сообщения или имя могут оказаться очень длинными. Важно учесть это сразу. Сначала посмотрим на подход «поток, как вам нравится».

v1lydqyd9hjxwcgiamjof3meqrm.png

На рисунке выше содержимое второй карточки разбивается на несколько строк. Для такого компонента это некрасиво. Вот что нужно сделать, чтобы строки не разбивались:

  • Установите min-width: 0 для дочерних элементов flex. Зачем? Я расскажу позже.
  • Обрежьте текст через свойства overflow, white-space, и text-overflow. Я уже писал подробнее об обработке короткого и длинного контентов.

Я добавил к имени и абзацу код ниже:
.card__content__start h3,
.card__content__start p {
  overflow-x: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

Но этот код не решает нашу проблему автоматически, когда мы используем flexbox. Обратите внимание на то, что делает приведённый выше CSS:
4p64g8tmpnabz5e0tt4ofnnlfsq.png

И вот причина: flex-элементы не сжимаются сильнее минимального размера контента. Чтобы решить эту проблему, нужно установить min-width: 0 в .card__content и card__content__start.

dy63crech_c-vk0op8dwlhtvlny.png

Вторая часть


У сообщения каждого типа есть некий индикатор, мы должны учитывать индикаторы всех типов.
dfbabtyloelhsjyf3bguwfsmz3i.png

В этой части сосредоточимся на .card__content__end и на содержании внутри него.


У элемента .card__content__end не должно быть таких стилей, как цвет или размер шрифта, этот элемент будет служить только родительским элементом определённого компонента.

Новое сообщение


Я посмотрел, как Facebook работает с индикатором нового сообщения; оказалось, что это кнопка с надписью «Mark as read».

6ie0eq6pblsot8q2luo1s5vjab0.png

Не знаю, почему команда Facebook выбрала div, а не button. С встроенной кнопкой не нужны атрибуты role, aria-label и tabindex. Все они встроены в кнопку.

Единственный аватар около поста


Такой аватар ничем не отличается от аватара пользователя. В нем применяется элемент svg с атрибутом aria-label, который показывает имя пользователя.
dbcwdfedsmrvqzt8fibxpxerq9q.png

  

Несколько аватаров около поста


Если честно, это мой любимый вариант. Мне очень нравится, как это сделала команда Facebook.
x1nu9hdnqtnj_clzej9zowzwgle.png

Вы видите границу между двумя аватарами? Сначала кажется, что это граница CSS для первого аватара. Если вы так подумали, извините, но вы ошиблись, как и я вначале.

Граница сделана при помощи маски SVG. Да, вы не ослышались!

Маска работает так:

kcfewpohi7q9vsvzzy_6srvxvla.png

Невероятно. Конкретно здесь мне нравится пользоваться SVG.

Контент справа налево


Когда макет LTR (слева направо), а текст сообщения написан на арабском языке, направление текста тоже должно быть RTL (справа налево).
lhqzzxyfy33t-9_sfkkd9ba-8_q.png

Элемент .card__content__start — это flex-контейнер, поэтому дочерние элементы будут автоматически переворачиваться в зависимости от значения свойства direction у компонента или корневого элемента. Такое поведение можно добавить динамически, в зависимости от языка текста.


Переворачиваем компонент


Если пользователь выбрал направление справа налево (например, арабский язык) для всего пользовательского интерфейса, то компонент нужно перевернуть.
e_wevrp51d0t-ihiiwfhqojbxp0.png

Элементы размещаются с помощью flexbox, поэтому нужно только перевернуть поля.

/* LTR */
.card__content__end {
  margin-left: 12px;
}

/* LTR */
.card__content__end {
  margin-right: 12px;
}

Доступность


Работа с клавиатуры


Продукт, который работает с миллиардами пользователей, должен быть доступен для всех. Что касается компонента из этой статьи, я протестировал его в Chrome и Firefox и заметил такие проблемы:
  • Стили фокуса отлично работают в Chrome, но в Firefox нет визуальной подсказки.
  • Фокуса на меню действий, которое появляется при наведении курсора, можно добиться в Firefox, и я не могу получить к нему доступ с клавиатуры в Chrome.

bqxgkuaa0khihejt9oe-8ivyszi.png

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

y6lf5xlggwmoypghtcev1ooui5a.png

К сожалению, в Chrome я не смог достучаться до меню действий с помощью клавиатуры.

Список карточек


В списке карточек прописаны некоторые роли ARIA. Этот список содержит строки и выглядит как сетка. В каждой строке может быть одна или несколько ячеек.

Несколько аватаров


Для группового чата есть несколько аватарок индикатора просмотров. Здесь роли ARIA располагают ячейки в ряд.

Посмотрите на демо с сайта Codepen. Всех вариантов здесь нет, я просто проверял их.

Заключение


В этой статье я хотел бы подчеркнуть, что простейший компонент требует огромной работы. Между прочим, все объяснения выше касались только HTML и CSS. А как насчёт JavaScript? Это уже другая история.

Я наслаждался работой, пока писал эту статью, и обязательно поработаю над чем-то подобным в будущем. И еще рад сообщить вам, что я написал электронную книгу об отладке CSS. Если вам интересно, кликните по ссылке debuggingcss.com и посмотрите книгу бесплатно. Вам понравился мой контент? Тогда вы можете заплатить за мой кофе. Большое спасибо!



Другие профессии и курсы
ПРОФЕССИИ

КУРСЫ

© Habrahabr.ru