[Перевод] Искусство компонентов. Пишем карточку контакта Facebook Messenger
Начнем
Я беру очень простой компонент Facebook Messenger, посмотрите на скриншот ниже:
В этом сайдбаре списком карточек перечисляются люди, которым я писал на Facebook. Здесь меня интересует только карточка. Как вы напишете её на HTML/CSS? Да очень легко, правда? Есть соблазн сказать, что это всего лишь картинка и слой рядом с ней. Вот о чём можно подумать:
Случай выше имеет место, если вы только учитесь или соревнуетесь на 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%;
}
Я выделил несколько строчек, их я хочу объяснить:
- Использовался flexbox, потому что у нас горизонтальный дизайн, а flexbox хорошо подходит для него.
- Дочерние элементы нужно центрировать вертикально.
- Значок позиционирован абсолютно, также он центрируется вертикально.
Ломаем компонент
Здесь нет ничего плохого, но компонент не масштабируется, так что покажу другой вариант:
Синий значок справа означает, что пришло новое сообщение, которое я ещё не открывал. Зелёный цвет на аватаре показывает, что пользователь сейчас в сети.
Обратите внимание: у нас есть два новых значка. Как лучше добавить их на карточку? Если вы обратитесь к CSS, который я написал для самого первого компонента, то увидите, что там есть класс .card_seen
для маленьких аваторов пользователей справа. В этом варианте .card_seen
должен быть заменён синим значком. С уже написанными HTML и CSS, не изменив HTML, написать такое невозможно.
Для ясности уточню, что вариант, который я показываю, — только поверхность возможностей. У компонента много вариаций и случаев использования.
Все вариации
Ниже я показываю все вариации компонента. Очень старался описать их все (да, я нарисовал всё это вручную).
Но и этого мало: мы должны учитывать стили тёмной темы.
В этой статье мы вместе выясним, как лучше всего писать надёжные компоненты, которые не ломаются после изменений и в самых разных расположениях. Приступим!
Интервалы
Прежде всего я внимательно изучил интервалы каждого элемента UI, работая с рисунками ниже. Видите, как много интервалов и размеров в этом простом компоненте.
В разработке и реализации UI на HTML и CSS одна из ключевых вещей, которой нужно уделить внимание, — это интервалы. Если интервалы недооценить, позже, возможно, придётся изменить сам UI.
Области компонента
Чтобы написать приличный компонент, нужно сначала тщательно продумать разметку. У нас есть две области компонента с несколькими вариациями: аватаром и областью контента.
Аватар
Чтобы работать над HTML аватара, сначала нужно разобраться с его состояниями. Вот возможные варианты:
- Один аватар.
- Один аватар со значком онлайн-статуса.
- Несколько аватаров для группового чата.
- Несколько аватаров со значком онлайн-статуса.
Учитывая HTML ниже, мы хотим удостовериться, что
.card__avatar
работает со всеми вариантами аватаров выше.
Один аватар
Давайте увеличим масштаб HTML и сосредоточимся на первом варианте, то есть на одном аватаре. У аватара должна быть граница (или внутренняя тень), чтобы он выглядел как круг, даже если он полностью белый.
В CSS невозможно применить внутреннюю box-shadow
к элементу img
. У нас есть два варианта:
- Дополнительный
div
с прозрачнымborder
. - Можно написать
svg
.
Цель внутренней границы — показать контур вокруг аватара в случаях, когда у нас:
- Полностью белый аватар светлой темы;
- Полностью чёрный аватар тёмной темы.
Без внутренней границы полностью белый аватар смешается с родительским фоном. Это случится и с тёмной темой. Вот что отрисовывается с внутренней границей и без неё.
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;
}
Оба решения хороши, когда строится один аватар только как пример. Всё станет интереснее, когда мы добавим элемент значка онлайн-статуса.
Единственный аватар со значком онлайн-статуса
В режиме светлой темы зелёный значок обведён белой границей. Но в тёмном режиме значок должен быть вырезан из самого аватара. Другими словами, нужно применить маску.
Как это сделать? Оказывается, если воспользоваться SVG-решением для того самого единственного аватара, проблема легко решается при помощи маски SVG.
Позвольте объяснить этот код:
- Круг маскирует аватар.
- В правом нижнем углу аватара вырезается маленький кружок.
- Группа, которая содержит
circle
иimage
для прозрачной внутренней границы.
Вот рисунок, который объясняет, как несколько окружностей работают в качестве маски. Это какая-то магия, правда?
Так выглядит 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);
}
Несколько аватаров в групповом чате
В случае чата с несколькими людьми в зоне аватаров находятся два аватара, каждый будет располагаться в правом верхнем и левом нижнем углах соответственно. Чтобы выровнять один и несколько аватаров, нужно установить фиксированный размер для их родительского элемента.
.card__avatar {
width: 56px;
height: 56px;
}
Этот вариант требует изменить разметку вот так:
.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;
}
Контент
В этой области пользователь увидит имя человека, с которым общается, а также текст сообщения или действие.
Я могу представить разделённую на две части разметку, одна — для текста (имени, сообщения или действия), а вторая — для индикатора справа (новое сообщение, увиденное, приглушённое, отправленное).
Первая часть
Давайте внимательно посмотрим на разметку области контента.
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).
Сообщения или имя могут оказаться очень длинными. Важно учесть это сразу. Сначала посмотрим на подход «поток, как вам нравится».
На рисунке выше содержимое второй карточки разбивается на несколько строк. Для такого компонента это некрасиво. Вот что нужно сделать, чтобы строки не разбивались:
- Установите
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:
И вот причина: flex-элементы не сжимаются сильнее минимального размера контента. Чтобы решить эту проблему, нужно установить min-width: 0
в .card__content
и card__content__start
.
Вторая часть
У сообщения каждого типа есть некий индикатор, мы должны учитывать индикаторы всех типов.
В этой части сосредоточимся на .card__content__end
и на содержании внутри него.
У элемента
.card__content__end
не должно быть таких стилей, как цвет или размер шрифта, этот элемент будет служить только родительским элементом определённого компонента.Новое сообщение
Я посмотрел, как Facebook работает с индикатором нового сообщения; оказалось, что это кнопка с надписью «Mark as read».
Не знаю, почему команда Facebook выбрала div
, а не button
. С встроенной кнопкой не нужны атрибуты role
, aria-label
и tabindex
. Все они встроены в кнопку.
Единственный аватар около поста
Такой аватар ничем не отличается от аватара пользователя. В нем применяется элемент
svg
с атрибутом aria-label
, который показывает имя пользователя.
Несколько аватаров около поста
Если честно, это мой любимый вариант. Мне очень нравится, как это сделала команда Facebook.
Вы видите границу между двумя аватарами? Сначала кажется, что это граница CSS для первого аватара. Если вы так подумали, извините, но вы ошиблись, как и я вначале.
Граница сделана при помощи маски SVG. Да, вы не ослышались!
Маска работает так:
Невероятно. Конкретно здесь мне нравится пользоваться SVG.
Контент справа налево
Когда макет LTR (слева направо), а текст сообщения написан на арабском языке, направление текста тоже должно быть RTL (справа налево).
Элемент .card__content__start
— это flex-контейнер, поэтому дочерние элементы будут автоматически переворачиваться в зависимости от значения свойства direction
у компонента или корневого элемента. Такое поведение можно добавить динамически, в зависимости от языка текста.
Переворачиваем компонент
Если пользователь выбрал направление справа налево (например, арабский язык) для всего пользовательского интерфейса, то компонент нужно перевернуть.
Элементы размещаются с помощью flexbox, поэтому нужно только перевернуть поля.
/* LTR */
.card__content__end {
margin-left: 12px;
}
/* LTR */
.card__content__end {
margin-right: 12px;
}
Доступность
Работа с клавиатуры
Продукт, который работает с миллиардами пользователей, должен быть доступен для всех. Что касается компонента из этой статьи, я протестировал его в Chrome и Firefox и заметил такие проблемы:
- Стили фокуса отлично работают в Chrome, но в Firefox нет визуальной подсказки.
- Фокуса на меню действий, которое появляется при наведении курсора, можно добиться в Firefox, и я не могу получить к нему доступ с клавиатуры в Chrome.
И, чтобы дать вам больше контекста, напомню: при наведении курсора появляется меню действий. Но я активно пользуюсь клавиатурой и ожидаю, что смогу работать с меню через неё.
К сожалению, в Chrome я не смог достучаться до меню действий с помощью клавиатуры.
Список карточек
В списке карточек прописаны некоторые роли ARIA. Этот список содержит строки и выглядит как сетка. В каждой строке может быть одна или несколько ячеек.
Несколько аватаров
Для группового чата есть несколько аватарок индикатора просмотров. Здесь роли
ARIA
располагают ячейки в ряд.
Посмотрите на демо с сайта Codepen. Всех вариантов здесь нет, я просто проверял их.
Заключение
В этой статье я хотел бы подчеркнуть, что простейший компонент требует огромной работы. Между прочим, все объяснения выше касались только HTML и CSS. А как насчёт JavaScript? Это уже другая история.
Я наслаждался работой, пока писал эту статью, и обязательно поработаю над чем-то подобным в будущем. И еще рад сообщить вам, что я написал электронную книгу об отладке CSS. Если вам интересно, кликните по ссылке debuggingcss.com и посмотрите книгу бесплатно. Вам понравился мой контент? Тогда вы можете заплатить за мой кофе. Большое спасибо!
КУРСЫ