Вполне возможно оценить компонент и сказать, что он легко пишется на HTML и CSS. Соглашусь, это легко, когда вы работаете, только чтобы практиковаться, но в реальном проекте всё по-другому. Идеальный адаптивный компонент, который вы только что создали, быстро перестаёт работать, когда сталкивается с реальным контентом настоящего проекта. Почему? Потому, что, пока вы рассуждаете о разработке компонента, вы можете упустить крайние случаи. Сегодня, специально к старту нового потока нашего курса по веб-разработке, поделюсь переводом статьи, в которой показан простой на первый взгляд компонент, за которым стоит огромная работа. Ради реалистичности это будет пример прямо из Facebook Messenger.
![](https://habrastorage.org/r/w780q1/webt/vw/uq/7_/vwuq7_x3nzmpqsu4rxn6boagxdg.jpeg)
Я беру очень простой компонент Facebook Messenger, посмотрите на скриншот ниже:
![](https://habrastorage.org/r/w1560/webt/lx/qj/yy/lxqjyyzxefqp50onyemtfqiue9a.png)
В этом сайдбаре списком карточек перечисляются люди, которым я писал на Facebook. Здесь меня интересует только карточка. Как вы напишете её на HTML/CSS? Да очень легко, правда? Есть соблазн сказать, что это всего лишь картинка и слой рядом с ней. Вот о чём можно подумать:
![](https://habrastorage.org/r/w1560/webt/jy/v6/fc/jyv6fcnrmtszfarm1bd3i2yvbda.png)
Случай выше имеет место, если вы только учитесь или соревнуетесь на UI-челлендже, но когда хочется написать надёжную карточку, которая растягивается, то пример выше быстро перестаёт работать. Посмотрим на простой вариант:
Я выделил несколько строчек, их я хочу объяснить:
Здесь нет ничего плохого, но компонент не масштабируется, так что покажу другой вариант:
![](https://habrastorage.org/r/w1560/webt/0w/hh/hj/0whhhjvp0vfclrlralsb9sh5fa8.png)
Синий значок справа означает, что пришло новое сообщение, которое я ещё не открывал. Зелёный цвет на аватаре показывает, что пользователь сейчас в сети.
Обратите внимание: у нас есть два новых значка. Как лучше добавить их на карточку? Если вы обратитесь к CSS, который я написал для самого первого компонента, то увидите, что там есть класс
Для ясности уточню, что вариант, который я показываю, — только поверхность возможностей. У компонента много вариаций и случаев использования.
Ниже я показываю все вариации компонента. Очень старался описать их все (да, я нарисовал всё это вручную).
![](https://habrastorage.org/r/w1560/webt/sb/6t/ej/sb6tejuiit2-ftjqlqrfqzugaue.png)
Но и этого мало: мы должны учитывать стили тёмной темы.
![](https://habrastorage.org/r/w1560/webt/2h/ky/y5/2hkyy5hj2uug6jdmh6rrr-a9k-q.png)
В этой статье мы вместе выясним, как лучше всего писать надёжные компоненты, которые не ломаются после изменений и в самых разных расположениях. Приступим!
Прежде всего я внимательно изучил интервалы каждого элемента UI, работая с рисунками ниже. Видите, как много интервалов и размеров в этом простом компоненте.
![](https://habrastorage.org/r/w1560/webt/c3/1a/wg/c31awgc4ybov46siw_zvxi7r6uk.png)
В разработке и реализации UI на HTML и CSS одна из ключевых вещей, которой нужно уделить внимание, — это интервалы. Если интервалы недооценить, позже, возможно, придётся изменить сам UI.
Чтобы написать приличный компонент, нужно сначала тщательно продумать разметку. У нас есть две области компонента с несколькими вариациями: аватаром и областью контента.
![](https://habrastorage.org/r/w1560/webt/x0/wu/ys/x0wuysa0upjkaqqvbcn1ccdyefy.png)
Чтобы работать над HTML аватара, сначала нужно разобраться с его состояниями. Вот возможные варианты:
Учитывая HTML ниже, мы хотим удостовериться, что
Давайте увеличим масштаб HTML и сосредоточимся на первом варианте, то есть на одном аватаре. У аватара должна быть граница (или внутренняя тень), чтобы он выглядел как круг, даже если он полностью белый.
![](https://habrastorage.org/r/w1560/webt/ap/ul/5-/apul5-mimlkxbnn0ef8pwygkl08.png)
В CSS невозможно применить внутреннюю
Цель внутренней границы — показать контур вокруг аватара в случаях, когда у нас:
![](https://habrastorage.org/r/w1560/webt/4r/jz/ed/4rjzedirboabytj06d-jhoqswsa.png)
Без внутренней границы полностью белый аватар смешается с родительским фоном. Это случится и с тёмной темой. Вот что отрисовывается с внутренней границей и без неё.
![](https://habrastorage.org/r/w1560/webt/xx/ld/cg/xxldcgz7hv3vt-rd2rj-jqufbqo.png)
В этом решении дополнительный элемент (здесь это
Это решение работает, но у него есть ограничения, о которых я скоро расскажу.
Работаем с
В этом решении воспользуемся элементом
Оба решения хороши, когда строится один аватар только как пример. Всё станет интереснее, когда мы добавим элемент значка онлайн-статуса.
В режиме светлой темы зелёный значок обведён белой границей. Но в тёмном режиме значок должен быть вырезан из самого аватара. Другими словами, нужно применить маску.
![](https://habrastorage.org/r/w1560/webt/mv/1n/aa/mv1naahbjk96jdkb8-bxdp_absm.png)
Как это сделать? Оказывается, если воспользоваться SVG-решением для того самого единственного аватара, проблема легко решается при помощи маски SVG.
Позвольте объяснить этот код:
Вот рисунок, который объясняет, как несколько окружностей работают в качестве маски. Это какая-то магия, правда?
![](https://habrastorage.org/r/w1560/webt/az/7k/2q/az7k2qqokbbwrgjnb5n7l97e_4o.png)
Так выглядит HTML аватара со значком онлайн-статуса.
Когда подобный этому компонент должен адаптироваться под разные темы, настоятельно рекомендуется хранить изменяемые цвета в переменных СSS.
![](https://habrastorage.org/webt/vc/ze/ay/vczeayquyuqpmvhj-d2qvtnteho.gif)
В случае чата с несколькими людьми в зоне аватаров находятся два аватара, каждый будет располагаться в правом верхнем и левом нижнем углах соответственно. Чтобы выровнять один и несколько аватаров, нужно установить фиксированный размер для их родительского элемента.
![](https://habrastorage.org/r/w1560/webt/d0/7y/6y/d07y6yo-mebl2x5x9ilszlejsxw.png)
Этот вариант требует изменить разметку вот так:
![](https://habrastorage.org/r/w1560/webt/_p/wn/g-/_pwng-wxxdmeoae31pwabq82lp8.png)
В этой области пользователь увидит имя человека, с которым общается, а также текст сообщения или действие.
![](https://habrastorage.org/r/w1560/webt/rc/8f/5j/rc8f5jpcfvrv0ptdoigajhidmmw.png)
Я могу представить разделённую на две части разметку, одна — для текста (имени, сообщения или действия), а вторая — для индикатора справа (новое сообщение, увиденное, приглушённое, отправленное).
![](https://habrastorage.org/r/w1560/webt/xt/an/yx/xtanyx97m-u1ii7gy_ue8vaibii.png)
Давайте внимательно посмотрим на разметку области контента.
С кодом выше область содержимого выглядит, как показано ниже (это скриншот из Firefox).
![](https://habrastorage.org/r/w1560/webt/m0/7y/rh/m07yrh2rdtk74mrm90zxdny6_og.png)
Сообщения или имя могут оказаться очень длинными. Важно учесть это сразу. Сначала посмотрим на подход «поток, как вам нравится».
![](https://habrastorage.org/r/w1560/webt/v1/ly/dq/v1lydqyd9hjxwcgiamjof3meqrm.png)
На рисунке выше содержимое второй карточки разбивается на несколько строк. Для такого компонента это некрасиво. Вот что нужно сделать, чтобы строки не разбивались:
Я добавил к имени и абзацу код ниже:
Но этот код не решает нашу проблему автоматически, когда мы используем flexbox. Обратите внимание на то, что делает приведённый выше CSS:
![](https://habrastorage.org/r/w1560/webt/4p/64/g8/4p64g8tmpnabz5e0tt4ofnnlfsq.png)
И вот причина: flex-элементы не сжимаются сильнее минимального размера контента. Чтобы решить эту проблему, нужно установить
![](https://habrastorage.org/r/w1560/webt/dy/63/cr/dy63crech_c-vk0op8dwlhtvlny.png)
У сообщения каждого типа есть некий индикатор, мы должны учитывать индикаторы всех типов.
![](https://habrastorage.org/r/w1560/webt/df/ba/bt/dfbabtyloelhsjyf3bguwfsmz3i.png)
В этой части сосредоточимся на
У элемента
Я посмотрел, как Facebook работает с индикатором нового сообщения; оказалось, что это кнопка с надписью «Mark as read».
![](https://habrastorage.org/r/w1560/webt/6i/e0/eq/6ie0eq6pblsot8q2luo1s5vjab0.png)
Не знаю, почему команда Facebook выбрала
Такой аватар ничем не отличается от аватара пользователя. В нем применяется элемент
![](https://habrastorage.org/r/w1560/webt/db/cw/df/dbcwdfedsmrvqzt8fibxpxerq9q.png)
Если честно, это мой любимый вариант. Мне очень нравится, как это сделала команда Facebook.
![](https://habrastorage.org/r/w1560/webt/x1/nu/9h/x1nu9hdnqtnj_clzej9zowzwgle.png)
Вы видите границу между двумя аватарами? Сначала кажется, что это граница CSS для первого аватара. Если вы так подумали, извините, но вы ошиблись, как и я вначале.
Граница сделана при помощи маски SVG. Да, вы не ослышались!
Маска работает так:
![](https://habrastorage.org/r/w1560/webt/kc/fe/wp/kcfewpohi7q9vsvzzy_6srvxvla.png)
Невероятно. Конкретно здесь мне нравится пользоваться SVG.
Когда макет LTR (слева направо), а текст сообщения написан на арабском языке, направление текста тоже должно быть RTL (справа налево).
![](https://habrastorage.org/r/w1560/webt/lh/qz/zx/lhqzzxyfy33t-9_sfkkd9ba-8_q.png)
Элемент
Если пользователь выбрал направление справа налево (например, арабский язык) для всего пользовательского интерфейса, то компонент нужно перевернуть.
![](https://habrastorage.org/r/w1560/webt/e_/we/vr/e_wevrp51d0t-ihiiwfhqojbxp0.png)
Элементы размещаются с помощью flexbox, поэтому нужно только перевернуть поля.
Продукт, который работает с миллиардами пользователей, должен быть доступен для всех. Что касается компонента из этой статьи, я протестировал его в Chrome и Firefox и заметил такие проблемы:
![](https://habrastorage.org/r/w1560/webt/bq/xg/ku/bqxgkuaa0khihejt9oe-8ivyszi.png)
И, чтобы дать вам больше контекста, напомню: при наведении курсора появляется меню действий. Но я активно пользуюсь клавиатурой и ожидаю, что смогу работать с меню через неё.
![](https://habrastorage.org/r/w1560/webt/y6/lf/5x/y6lf5xlggwmoypghtcev1ooui5a.png)
К сожалению, в Chrome я не смог достучаться до меню действий с помощью клавиатуры.
В списке карточек прописаны некоторые роли ARIA. Этот список содержит строки и выглядит как сетка. В каждой строке может быть одна или несколько ячеек.
Для группового чата есть несколько аватарок индикатора просмотров. Здесь роли
Посмотрите на демо с сайта Codepen. Всех вариантов здесь нет, я просто проверял их.
В этой статье я хотел бы подчеркнуть, что простейший компонент требует огромной работы. Между прочим, все объяснения выше касались только HTML и CSS. А как насчёт JavaScript? Это уже другая история.
Я наслаждался работой, пока писал эту статью, и обязательно поработаю над чем-то подобным в будущем. И еще рад сообщить вам, что я написал электронную книгу об отладке CSS. Если вам интересно, кликните по ссылке debuggingcss.com и посмотрите книгу бесплатно. Вам понравился мой контент? Тогда вы можете заплатить за мой кофе. Большое спасибо!
![](https://habrastorage.org/webt/vw/uq/7_/vwuq7_x3nzmpqsu4rxn6boagxdg.jpeg)
Начнем
Я беру очень простой компонент Facebook Messenger, посмотрите на скриншот ниже:
![](https://habrastorage.org/webt/lx/qj/yy/lxqjyyzxefqp50onyemtfqiue9a.png)
В этом сайдбаре списком карточек перечисляются люди, которым я писал на Facebook. Здесь меня интересует только карточка. Как вы напишете её на HTML/CSS? Да очень легко, правда? Есть соблазн сказать, что это всего лишь картинка и слой рядом с ней. Вот о чём можно подумать:
![](https://habrastorage.org/webt/jy/v6/fc/jyv6fcnrmtszfarm1bd3i2yvbda.png)
Случай выше имеет место, если вы только учитесь или соревнуетесь на UI-челлендже, но когда хочется написать надёжную карточку, которая растягивается, то пример выше быстро перестаёт работать. Посмотрим на простой вариант:
<div class="card">
<img class="card__image" src="assets/shadeed.jpg" alt="" />
<div>
<h3>Ahmad Shadeed</h3>
<p>You: Thanks, sounds good! . 8hr</p>
<img class="card__seen" src="assets/shadeed.jpg" alt="" />
</div>
</div>
.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 хорошо подходит для него.
- Дочерние элементы нужно центрировать вертикально.
- Значок позиционирован абсолютно, также он центрируется вертикально.
Ломаем компонент
Здесь нет ничего плохого, но компонент не масштабируется, так что покажу другой вариант:
![](https://habrastorage.org/webt/0w/hh/hj/0whhhjvp0vfclrlralsb9sh5fa8.png)
Синий значок справа означает, что пришло новое сообщение, которое я ещё не открывал. Зелёный цвет на аватаре показывает, что пользователь сейчас в сети.
Обратите внимание: у нас есть два новых значка. Как лучше добавить их на карточку? Если вы обратитесь к CSS, который я написал для самого первого компонента, то увидите, что там есть класс
.card_seen
для маленьких аваторов пользователей справа. В этом варианте .card_seen
должен быть заменён синим значком. С уже написанными HTML и CSS, не изменив HTML, написать такое невозможно.Для ясности уточню, что вариант, который я показываю, — только поверхность возможностей. У компонента много вариаций и случаев использования.
Все вариации
Ниже я показываю все вариации компонента. Очень старался описать их все (да, я нарисовал всё это вручную).
![](https://habrastorage.org/webt/sb/6t/ej/sb6tejuiit2-ftjqlqrfqzugaue.png)
Но и этого мало: мы должны учитывать стили тёмной темы.
![](https://habrastorage.org/webt/2h/ky/y5/2hkyy5hj2uug6jdmh6rrr-a9k-q.png)
В этой статье мы вместе выясним, как лучше всего писать надёжные компоненты, которые не ломаются после изменений и в самых разных расположениях. Приступим!
Интервалы
Прежде всего я внимательно изучил интервалы каждого элемента UI, работая с рисунками ниже. Видите, как много интервалов и размеров в этом простом компоненте.
![](https://habrastorage.org/webt/c3/1a/wg/c31awgc4ybov46siw_zvxi7r6uk.png)
В разработке и реализации UI на HTML и CSS одна из ключевых вещей, которой нужно уделить внимание, — это интервалы. Если интервалы недооценить, позже, возможно, придётся изменить сам UI.
Области компонента
Чтобы написать приличный компонент, нужно сначала тщательно продумать разметку. У нас есть две области компонента с несколькими вариациями: аватаром и областью контента.
Аватар
![](https://habrastorage.org/webt/x0/wu/ys/x0wuysa0upjkaqqvbcn1ccdyefy.png)
Чтобы работать над HTML аватара, сначала нужно разобраться с его состояниями. Вот возможные варианты:
- Один аватар.
- Один аватар со значком онлайн-статуса.
- Несколько аватаров для группового чата.
- Несколько аватаров со значком онлайн-статуса.
Учитывая HTML ниже, мы хотим удостовериться, что
.card__avatar
работает со всеми вариантами аватаров выше.<div class="card">
<div class="card__avatar"></div>
<div class="card__content">
<!-- Name, message, badge.. -->
</div>
</div>
Один аватар
Давайте увеличим масштаб HTML и сосредоточимся на первом варианте, то есть на одном аватаре. У аватара должна быть граница (или внутренняя тень), чтобы он выглядел как круг, даже если он полностью белый.
![](https://habrastorage.org/webt/ap/ul/5-/apul5-mimlkxbnn0ef8pwygkl08.png)
В CSS невозможно применить внутреннюю
box-shadow
к элементу img
. У нас есть два варианта:- Дополнительный
div
с прозрачнымborder
.
- Можно написать
svg
.
Цель внутренней границы — показать контур вокруг аватара в случаях, когда у нас:
- Полностью белый аватар светлой темы;
- Полностью чёрный аватар тёмной темы.
![](https://habrastorage.org/webt/4r/jz/ed/4rjzedirboabytj06d-jhoqswsa.png)
Без внутренней границы полностью белый аватар смешается с родительским фоном. Это случится и с тёмной темой. Вот что отрисовывается с внутренней границей и без неё.
![](https://habrastorage.org/webt/xx/ld/cg/xxldcgz7hv3vt-rd2rj-jqufbqo.png)
div
для внутренней границы
В этом решении дополнительный элемент (здесь это
div
) абсолютно расположен над изображением с непрозрачностью 0.1
.<div class="card__avatar">
<img src="assets/shadeed.jpg" alt="" />
<div class="border"></div>
</div>
.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 отлично подходит для этого.<svg role="none" style="height: 56px; width: 56px">
<mask id="circle">
<circle cx="28" cy="28" fill="white" r="28"></circle>
</mask>
<g mask="url(#circle)">
<image
x="0"
y="0"
height="100%"
preserveAspectRatio="xMidYMid slice"
width="100%"
xlink:href="/assets/shadeed.jpg"
style="height: 56px; width: 56px"
></image>
<circle class="border" cx="28" cy="28" r="28"></circle>
</g>
</svg>
.border {
stroke-width: 3;
stroke: rgba(0, 0, 0, 0.1);
fill: none;
}
Оба решения хороши, когда строится один аватар только как пример. Всё станет интереснее, когда мы добавим элемент значка онлайн-статуса.
Единственный аватар со значком онлайн-статуса
В режиме светлой темы зелёный значок обведён белой границей. Но в тёмном режиме значок должен быть вырезан из самого аватара. Другими словами, нужно применить маску.
![](https://habrastorage.org/webt/mv/1n/aa/mv1naahbjk96jdkb8-bxdp_absm.png)
Как это сделать? Оказывается, если воспользоваться SVG-решением для того самого единственного аватара, проблема легко решается при помощи маски SVG.
<svg role="none" style="height: 56px; width: 56px">
<mask id="circle">
<!-- [1] -->
<circle cx="28" cy="28" fill="white" r="28"></circle>
<!-- [2] -->
<circle cx="48" cy="48" fill="black" r="7"></circle>
</mask>
<!-- [3] -->
<g mask="url(#circle)">
<image
x="0"
y="0"
height="100%"
preserveAspectRatio="xMidYMid slice"
width="100%"
xlink:href="/assets/shadeed.jpg"
style="height: 56px; width: 56px"
></image>
<circle class="border" cx="28" cy="28" r="28"></circle>
</g>
</svg>
Позвольте объяснить этот код:
- Круг маскирует аватар.
- В правом нижнем углу аватара вырезается маленький кружок.
- Группа, которая содержит
circle
иimage
для прозрачной внутренней границы.
Вот рисунок, который объясняет, как несколько окружностей работают в качестве маски. Это какая-то магия, правда?
![](https://habrastorage.org/webt/az/7k/2q/az7k2qqokbbwrgjnb5n7l97e_4o.png)
Так выглядит HTML аватара со значком онлайн-статуса.
<div class="card__avatar">
<svg role="none" style="height: 56px; width: 56px">
<mask id="circle">
<circle cx="28" cy="28" fill="white" r="28"></circle>
<circle cx="48" cy="48" fill="black" r="7"></circle>
</mask>
<g mask="url(#circle)">
<image
x="0"
y="0"
height="100%"
preserveAspectRatio="xMidYMid slice"
width="100%"
xlink:href="/assets/shadeed.jpg"
style="height: 56px; width: 56px"
></image>
<circle class="border" cx="28" cy="28" r="28"></circle>
</g>
</svg>
<div class="badge"></div>
</div>
.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);
}
![](https://habrastorage.org/webt/vc/ze/ay/vczeayquyuqpmvhj-d2qvtnteho.gif)
Несколько аватаров в групповом чате
В случае чата с несколькими людьми в зоне аватаров находятся два аватара, каждый будет располагаться в правом верхнем и левом нижнем углах соответственно. Чтобы выровнять один и несколько аватаров, нужно установить фиксированный размер для их родительского элемента.
.card__avatar {
width: 56px;
height: 56px;
}
![](https://habrastorage.org/webt/d0/7y/6y/d07y6yo-mebl2x5x9ilszlejsxw.png)
Этот вариант требует изменить разметку вот так:
<div class="card__avatar card__avatar--multiple">
<svg
class="avatar avatar-1"
role="none"
style="height: 36px; width: 36px"
></svg>
<svg
class="avatar avatar-2"
role="none"
style="height: 36px; width: 36px"
></svg>
<div class="badge"></div>
</div>
.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;
}
![](https://habrastorage.org/webt/_p/wn/g-/_pwng-wxxdmeoae31pwabq82lp8.png)
Контент
В этой области пользователь увидит имя человека, с которым общается, а также текст сообщения или действие.
![](https://habrastorage.org/webt/rc/8f/5j/rc8f5jpcfvrv0ptdoigajhidmmw.png)
Я могу представить разделённую на две части разметку, одна — для текста (имени, сообщения или действия), а вторая — для индикатора справа (новое сообщение, увиденное, приглушённое, отправленное).
![](https://habrastorage.org/webt/xt/an/yx/xtanyx97m-u1ii7gy_ue8vaibii.png)
Первая часть
Давайте внимательно посмотрим на разметку области контента.
<div class="card__content">
<div class="card__content__start">
<h3>Ahmad Shadeed</h3>
<div class="row">
<p>You: Thanks, sounds good. What about doing a webinar, too?</p>
<span class="separator">.</span>
<time>8hr</time>
</div>
</div>
<div class="card__content__end">
<!-- The indicator (new message, seen, muted, sent) -->
</div>
</div>
.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).
![](https://habrastorage.org/webt/m0/7y/rh/m07yrh2rdtk74mrm90zxdny6_og.png)
Сообщения или имя могут оказаться очень длинными. Важно учесть это сразу. Сначала посмотрим на подход «поток, как вам нравится».
![](https://habrastorage.org/webt/v1/ly/dq/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:
![](https://habrastorage.org/webt/4p/64/g8/4p64g8tmpnabz5e0tt4ofnnlfsq.png)
И вот причина: flex-элементы не сжимаются сильнее минимального размера контента. Чтобы решить эту проблему, нужно установить
min-width: 0
в .card__content
и card__content__start
.![](https://habrastorage.org/webt/dy/63/cr/dy63crech_c-vk0op8dwlhtvlny.png)
Вторая часть
У сообщения каждого типа есть некий индикатор, мы должны учитывать индикаторы всех типов.
![](https://habrastorage.org/webt/df/ba/bt/dfbabtyloelhsjyf3bguwfsmz3i.png)
В этой части сосредоточимся на
.card__content__end
и на содержании внутри него.<div class="card__content">
<div class="card__content__start">
<!-- The name and message -->
</div>
<div class="card__content__end">
<!-- The indicator (new message, seen, muted, sent) -->
</div>
</div>
У элемента
.card__content__end
не должно быть таких стилей, как цвет или размер шрифта, этот элемент будет служить только родительским элементом определённого компонента.Новое сообщение
Я посмотрел, как Facebook работает с индикатором нового сообщения; оказалось, что это кнопка с надписью «Mark as read».
<div role="button" aria-label="Mark as read" tabindex="0"></div>
![](https://habrastorage.org/webt/6i/e0/eq/6ie0eq6pblsot8q2luo1s5vjab0.png)
Не знаю, почему команда Facebook выбрала
div
, а не button
. С встроенной кнопкой не нужны атрибуты role
, aria-label
и tabindex
. Все они встроены в кнопку.Единственный аватар около поста
Такой аватар ничем не отличается от аватара пользователя. В нем применяется элемент
svg
с атрибутом aria-label
, который показывает имя пользователя.![](https://habrastorage.org/webt/db/cw/df/dbcwdfedsmrvqzt8fibxpxerq9q.png)
<svg aria-label="Ahmad Shadeed" role="img">
<!-- Mask and image -->
</svg>
Несколько аватаров около поста
Если честно, это мой любимый вариант. Мне очень нравится, как это сделала команда Facebook.
![](https://habrastorage.org/webt/x1/nu/9h/x1nu9hdnqtnj_clzej9zowzwgle.png)
Вы видите границу между двумя аватарами? Сначала кажется, что это граница CSS для первого аватара. Если вы так подумали, извините, но вы ошиблись, как и я вначале.
Граница сделана при помощи маски SVG. Да, вы не ослышались!
Маска работает так:
![](https://habrastorage.org/webt/kc/fe/wp/kcfewpohi7q9vsvzzy_6srvxvla.png)
Невероятно. Конкретно здесь мне нравится пользоваться SVG.
Контент справа налево
Когда макет LTR (слева направо), а текст сообщения написан на арабском языке, направление текста тоже должно быть RTL (справа налево).
![](https://habrastorage.org/webt/lh/qz/zx/lhqzzxyfy33t-9_sfkkd9ba-8_q.png)
Элемент
.card__content__start
— это flex-контейнер, поэтому дочерние элементы будут автоматически переворачиваться в зависимости от значения свойства direction
у компонента или корневого элемента. Такое поведение можно добавить динамически, в зависимости от языка текста.<div class="card__content">
<div class="card__content__start" style="direction: rtl"></div>
<div class="card__content__end"></div>
</div>
Переворачиваем компонент
Если пользователь выбрал направление справа налево (например, арабский язык) для всего пользовательского интерфейса, то компонент нужно перевернуть.
![](https://habrastorage.org/webt/e_/we/vr/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.
![](https://habrastorage.org/webt/bq/xg/ku/bqxgkuaa0khihejt9oe-8ivyszi.png)
И, чтобы дать вам больше контекста, напомню: при наведении курсора появляется меню действий. Но я активно пользуюсь клавиатурой и ожидаю, что смогу работать с меню через неё.
![](https://habrastorage.org/webt/y6/lf/5x/y6lf5xlggwmoypghtcev1ooui5a.png)
К сожалению, в Chrome я не смог достучаться до меню действий с помощью клавиатуры.
Список карточек
В списке карточек прописаны некоторые роли ARIA. Этот список содержит строки и выглядит как сетка. В каждой строке может быть одна или несколько ячеек.
<div role="grid">
<div role="row">
<div role="gridcell">
<a href="#">
<!-- The component lives here -->
</a>
</div>
</div>
<div role="row">
<div role="gridcell">
<a href="#">
<!-- The component lives here -->
</a>
</div>
</div>
</div>
Несколько аватаров
Для группового чата есть несколько аватарок индикатора просмотров. Здесь роли
ARIA
располагают ячейки в ряд.<div role="grid">
<div role="row">
<!-- 1st avatar -->
<div role="cell"></div>
<!-- 2nd avatar -->
<div role="cell"></div>
</div>
</div>
Посмотрите на демо с сайта Codepen. Всех вариантов здесь нет, я просто проверял их.
Заключение
В этой статье я хотел бы подчеркнуть, что простейший компонент требует огромной работы. Между прочим, все объяснения выше касались только HTML и CSS. А как насчёт JavaScript? Это уже другая история.
Я наслаждался работой, пока писал эту статью, и обязательно поработаю над чем-то подобным в будущем. И еще рад сообщить вам, что я написал электронную книгу об отладке CSS. Если вам интересно, кликните по ссылке debuggingcss.com и посмотрите книгу бесплатно. Вам понравился мой контент? Тогда вы можете заплатить за мой кофе. Большое спасибо!
- Профессия Веб-разработчик
- Профессия Этичный хакер
- Обучение профессии Data Science
- Обучение профессии Data Analyst
Другие профессии и курсы
ПРОФЕССИИ
КУРСЫ
- Профессия Java-разработчик
- Профессия Frontend-разработчик
- Профессия C++ разработчик
- Профессия Разработчик игр на Unity
- Профессия iOS-разработчик с нуля
- Профессия Android-разработчик с нуля
КУРСЫ
- Курс «Алгоритмы и структуры данных»
- Курс по Machine Learning
- Курс «Python для веб-разработки»
- Курс по JavaScript
- Курс «Математика и Machine Learning для Data Science»
- Курс по аналитике данных
- Курс по DevOps