Разработка настоящих компонентов: блок сообщения Facebook Messenger

в 14:00, , рубрики: Блог компании RUVDS.com
Смесь любопытства и тяги к исследованиям снова привели меня к системе обмена сообщениями Facebook. Я уже изучал компоненты Facebook и писал об этом. Сейчас я обратил внимание на то, что в одни только блоки для вывода сообщений чата вложена огромная работа. На первый взгляд может показаться, что разработка компонента, реализующего чат — это просто, что у составных частей такого компонента будет не особенно много вариаций.

Если же вникнуть в тему работы с сообщениями, то окажется, что один только интерфейс чата — это такая штука, при создании которой нужно учесть невероятное количество деталей. Особенно — если это чат некоей платформы, сравнимой по масштабам с Facebook.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 1

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

Предварительные сведения

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 2

Блок сообщения

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 3

Разновидности блоков сообщений

Написание HTML- и CSS-кода для подобного компонента — это нетривиальная (но очень интересная) задача. В данном случае компонент чата должен быть динамическим для того чтобы справиться с выводом самого разного содержимого и служебной информации, для поддержки тем оформления и вывода текстов на разных языках.

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

Анатомия блока сообщений

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

Для начала мне хотелось бы обратить ваше внимание на то, что структура отправленных и полученных сообщений различается.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 4

Окно чата, LTR-язык

Если вы пишете слева направо (LTR, Left-To-Right), как, например, принято в английском языке, тогда блок с вашими сообщениями, синий, будет справа, а блок собеседника (серый) — слева.

Если же вы пользуетесь RTL-языком (Right-To-Left, справа налево), например — арабским, тогда блоки поменяются местами.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 5

Окно чата, RTL-язык

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 6

Внешний контейнер (Outer container), внутренний контейнер (Inner container), аватар пользователя (Avatar), разделитель (Spacer), меню действий (Actions Menu), область вывода сообщения (Bubble), область вывода состояния сообщения (Status)

Блок сообщения чата состоит из следующих частей:

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

Для того чтобы было понятнее — на следующем рисунке внешний и внутренние контейнеры отделены друг от друга.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 7

Внешний и внутренний контейнеры блока сообщения

Возможно, вы обратите внимание на то, что область для вывода аватара очень мала. А где сам аватар? Это — хороший вопрос.

Выше показана структура сообщения в том виде, в котором его видит тот, кто это сообщение отправил. Если же речь идёт о принятом сообщении, то оно будет выведено в блоке, представляющим собой, по большей части, зеркальное отражение вышерассмотренного блока. Я, исследуя возможность создания этого «зеркального» блока без необходимости разработки отдельного компонента, кое-что выяснил.

А именно, положение первого и последнего элементов блока (аватара и состояния сообщения) не зависит от того, кто отправил сообщение. Они «отражаются» лишь в том случае, если меняется направление вывода текста (LRT на RTL или наоборот). Вот ещё некоторые наблюдения:

  • Место для аватара зарезервировано даже в том случае, если аватар в нём не выводится. Если вы отправляете сообщение — это место будет пустым.
  • Но если кто-то отправляет сообщение вам — в месте для аватара будет выведено соответствующее изображение.
  • «Отражается» лишь внутренняя часть блока сообщения (внутренний контейнер), которая содержит поле для вывода содержимого сообщения, меню и разделитель.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 8

Блоки отправленного и принятого сообщений

У такого решения есть одна ценная особенность: он отлично подходит и для LTR макетов, пример которого рассмотрен выше, и для RTL-макетов. Обратите внимание на то, что весь внешний контейнер автоматически «переворачивается» при использовании RTL-макетов.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 9

LTR- и RTL-макеты блока отправленного сообщения

А вот — сводная схема, где показаны принятые и отправленные сообщения в LTR и RTL-исполнении.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 10

LTR- и RTL-макеты блока отправленных и принятых сообщений

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

Базовый HTML- и CSS-код

Вот как выглядит базовая структура блока сообщения в HTML:

<div class="message">
    <div class="message__outer">
        <div class="message__avatar"></div>
        <div class="message__inner">
            <div class="message__bubble"></div>
            <div class="message__actions"></div>
            <div class="message__spacer"></div>
        </div>
        <div class="message__status"></div>
    </div>
</div>

Вот базовые стили:

.message__outer {
    display: flex;
}

.message__inner {
    flex: 1;
    display: flex;
    flex-direction: row-reverse;
}

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 11

Базовый блок сообщения

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

Вот CSS-код:

.message__actions {
    width: 67px;
    padding-right: 5px;
}

.message__spacer {
    flex: 1;
}

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 12

Стилизация внутренней части блока сообщения

Полагаю, то, что меню назначена фиксированная ширина, объясняется следующими причинами:

  • Резервирование пространства для предотвращения сдвига макета.
  • Улучшение возможностей управления внутренней частью макета. Например, при стилизации области вывода сообщения нужно использовать свойство max-width. Может понадобиться вычесть ширину области меню из ширины области вывода сообщения.

Для того чтобы обеспечить правильный вывод блока сообщения в области просмотра маленькой ширины, нужно обратить внимание на следующее:

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

.message__bubble {
    max-width: calc(100% - 67px);
    overflow-wrap: break-word;
}

.message__actions {
    flex-shrink: 0;
}

Теперь поговорим о стилизации области вывода состояния сообщения и области вывода аватара (прямых потомков внешнего контейнера).

Работа с RTL- и LTR-текстами

Стоит упомянуть, что область вывода сообщения поддерживает представление текстов, написанных на RTL- и LTR-языках, благодаря использованию HTML-атрибута dir со значением auto.

<div dir="auto"></div>

Если в сообщении сначала был введён текст на RTL-языке (на арабском, например), то, даже если потом ввести текст на LTR-языке, он будет выровнен по правому краю.

И, аналогично, если сначала в поле был введён текст на LTR-языке (например — на английском), а потом — на RTL-языке, он будет выровнен по левому краю.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 13

Выравнивание текстов в сообщениях, где одновременно используются RTL- и LTR-языки

Стилизация области вывода состояния сообщения

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 14

Область вывода состояния сообщения

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 15

Различные значки, указывающие на состояние сообщения

Вот как выглядит HTML-разметка этого элемента:

<div class="message__status">
    <span class="avatar"></span>
</div>

Здесь, для выравнивания дочернего элемента по нижнему краю контейнера, использован Flexbox-макет. Обратите внимание на то, что свойству flex-direction назначено значение column.

.message__status {
    width: 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
}

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 16

Высокие сообщения и область вывода состояния сообщения

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 17

Значки, указывающие на состояние сообщения

Стилизация области вывода аватара отправителя сообщения

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

Вот HTML-код:

<div class="message__avatar">
    <img class="avatar" src="ahmad.jpg" alt="Photo of Ahmad Shadeed">
</div>

Вот — стили:

.message__avatar {
    padding-left: 6px;
    padding-right: 8px;
}

Когда обёртка аватара пуста, её ширина будет составлять 14px (сумма размеров полей 6px и 8px).

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 18

Пустой элемент-обёртка для вывода аватара

Если же речь идёт о просмотре принятого сообщения — в элементе-обёртке будет выведено изображение аватара размером 28x28.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 19

Элемент-обёртка с аватаром

Для того чтобы всё было бы предельно ясно — на следующем рисунке показаны элементы-обёртки аватара отправленного и полученного сообщения.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 20

Отправленное и полученное сообщения

Стилизация меню

С каждым сообщением может быть связан набор действий, представленных командами меню, выводимого при наведении указателя мыши на соответствующую область:

  • Работа с эмотиконами.
  • Ответ на сообщение или его цитирование.
  • Пересылка материалов сообщений (для сообщений с изображениями или видео).
  • Дополнительные команды меню (три точки).

Вот некоторые замечания по поводу этого меню:

  • У него есть правое поле в том случае, если речь идёт об области для вывода отправленного сообщения. В противном случае у него имеется левое поле.
  • Оно, для получателя сообщения, представлено в отражённом виде с помощью flex-direction: row-reverse. Стандартный порядок его вывода применяется при выводе собственных сообщений у отправителя этих сообщений.

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

Вот схема «отражения» области вывода меню при просмотре собственных и полученных сообщений.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 21

Область вывода меню в собственном и полученном сообщении

Если сообщение очень длинное, а так же — при отправке изображений или видео, меню должно быть центровано по вертикали. Этого можно добиться с использованием Flexbox-инструментов для выравнивания материалов.

Вот разметка:

<div class="message__actions">
    <ul class="menu"></ul>
</div>

Вот CSS-код:

.message__actions {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 67px;
    padding-right: 5px;
}

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 22

Выравнивание меню при выводе длинных сообщений

Как так получается? Дело в том, что если в сообщении имеется длинный текст или изображение — элемент .message__actions растянется так, чтобы соответствовать высоте родительского элемента. Это — стандартное поведение Flexbox-макетов. Мы можем этим воспользоваться для того чтобы отцентровать меню по вертикали.

Теперь, когда мы разобрались с базовым блоком сообщений — поговорим о вариациях таких блоков.

Вывод нескольких блоков сообщений

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 23

LTR- и RTL-макеты и различные блоки для вывода нескольких сообщений, отправленных или полученных друг за другом

Более того — области вывода сообщений меняются местами в LTR- и RTL-макетах. Хотелось бы мне, чтобы логические свойства border-radius пользовались бы достаточно хорошей поддержкой браузеров, позволяющей применять их в продакшне. Пока логические значения для свойств border-radius поддерживаются в браузере Chrome 89 (вышел 1 марта 2021 года).

Если бы эти свойства пользовались широкой поддержкой браузеров, то нашу задачу с их помощью можно было бы решить примерно так:

/* Отправитель, сообщения с синим фоном */

/* Первое сообщение */
.message--first .message__bubble {
    border-end-end-radius: 4px;
}

/* Сообщение, находящееся в середине набора сообщений */
.message--middle .message__bubble {
    border-start-end-radius: 4px;
    border-end-end-radius: 4px;
}

/* Последнее сообщение */
.message--last .message__bubble {
    border-start-end-radius: 4px;
}

Знаю, что «двойные» переменные, в названиях которых есть нечто вроде end-end, могут, на первый взгляд, показаться непонятными. Поэтому попробую раскрыть их смысл с помощью следующего рисунка.

Итак, у нас есть две оси (Block и Inline), каждая из них имеет значения start и end.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 24

LTR-макет

Если речь идёт о RTL-макете, то Start и End оси Inline меняются местами. При таком подходе нам не нужно переназначать значение для радиуса и менять направление. Обратите внимание на то, что на следующем рисунке, если сравнить его с предыдущим, Inline Start и Inline End поменялись местами.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 25

RTL-макет

Если вы хотите лучше разобраться с логическими CSS-свойствами — вот моя статья на эту тему.

Поддержка вывода изображений

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 26

Отправленное и принятое сообщения, содержащие изображения

Может показаться, что организовать поддержку вывода изображений разных размеров и обладающих различным соотношением сторон — это просто. Возможно, нужно всего лишь воспользоваться свойством max-width: 100% — и всё готово. Была у вас такая мысль? Но в случае с Facebook Messenger всё гораздо интереснее.

Когда пользователь выгружает изображение — его ширина задаётся во встроенном CSS-коде. Там ещё имеется и свойство padding-bottom: 56.66% для обеспечения отзывчивости изображения (это — так называемый «padding hack»).

Вот HTML-код:

<a class="image" href="#">
    <div class="image__main">
        <div class="image__element" style="width: 348.259px; ">
            <div class="image__aspectRatio" style="padding-bottom: 57.4286%">
                <div class="image__wrapper">
                    <img src="assets/thumb-1.png" alt="">
                </div>
            </div>
        </div>
    </div>
</a>

Вот — стили:

.image {
    display: block;
    border-radius: 18px;
    border: 1px solid #0006;
    overflow: hidden;
}

.image__main {
    max-width: 480px;
}

.image__element {
    max-width: 100%;
    position: relative;
}

.image__aspectRatio {
    position: relative;
}

.image__wrapper {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.image__wrapper img {
    display: block; 
    max-width: 100%; 
    max-height: 200px;
    width: 100%;
    height: 100%;
}

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

  • Максимальная ширина изображения — 480px.
  • Максимальная высота изображения — 200px.
  • Соотношение сторон изображения не должно меняться при изменении его размеров.
  • Если размеры изображения меньше вышеозначенных значений — оно должно выводиться в своём исходном состоянии.

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 27

Вывод изображений с разным соотношением сторон

Более того, важно помнить о том, что Flexbox-макет не сжимает изображения до размеров, которые меньше минимальных размеров содержимого такого макета. То есть — если довести ширину окна браузера до определённого значения — Flex-элемент не станет меньше, чем минимальные размеры его содержимого. Для того чтобы это исправить, нужно свойство min-width: 0.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 28

Макет, который нуждается в доработке

В моём демонстрационном коде Flex-элемент является прямым потомком message__outer, в нашем случае это — message__row.

.message__row {
    min-width: 0;
    /* другие стили */
}

Вот как всё должно выглядеть в том случае, если всё работает так, как должно работать.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 29

Доработанный макет

Может, у кого-то возникнет мысль о том, что тут можно было бы дать изображению фиксированную ширину и воспользоваться встроенным CSS. Но надо понимать, что за каждым решением, даже небольшим, принятым разработчиками, стоит некая веская причина. Полагаю, что в данном случае всё дело в загружаемом изображении. Если бы не был задан фиксированный размер изображения — мы столкнулись бы со сдвигами макета.

Вывод нескольких изображений

Если пользователь отправляет в чате сразу несколько изображений, соотношения их сторон не учитываются. Вместо этого каждое изображение размещается в квадратной области, а волшебное свойство object-fit позволяет не искажать и не растягивать изображения.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 30

Вывод нескольких изображений

.gallery {
    display: flex;
    flex-wrap: wrap;
    flex: 1;
    /* Отрицательное поле позволяет выровнять галерею с
    элементами того же уровня. */
    margin: -2px;
}

.gallery__item {
    /* Добавить смещение размером 2px вокруг каждого изображения, что даст
    4px для двух смежных изображений. */
    padding: 2;
}

.gallery__item--third {
    flex: 33.33%;
}

.gallery__item--half {
    flex: 50%;
}

Вот что здесь происходит:

  • Сетка для вывода изображений построена с использованием CSS Flexbox.
  • Ширина изображения составляет треть или половину ширины Flexbox-контейнера, что зависит от количества изображений.
  • Расстояние между изображениями регулируется путём настройки полей родительских элементов изображений.
  • Чтобы не оставлять ненужных пустых пространств вокруг границ галереи, во Flex-контейнере должно быть использовано отрицательное значение свойства padding.

Мне хотелось бы уделить особое внимание свойству padding: 2px, которое используется для настройки расстояния между изображениями. Оно не только позволяет добиться желаемого эффекта, но и отличается универсальностью, так как подходит для LTR- и RTL-макетов.

Если вы хотите углубиться в тему настройки расстояния между элементами с помощью CSS — взгляните на эту мою статью.

Цитирование сообщений

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 31

Процитированное сообщение и текст обычного сообщения

При выводе процитированного сообщения используются три основных элемента:

  • Заголовок, показывающий, кто кому ответил (например — «You replied to Ahmad»).
  • Процитированное сообщение, выведенное с использованием особого стиля.
  • Текст сообщения.

Присмотримся к структуре процитированного сообщения.

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

У сообщения (выведено серым) установлено большое значение свойства padding-bottom и отрицательное значение margin-bottom, что позволяет переместить текст процитированного сообщения выше, чем текст обычного сообщения. И наконец — его правый нижний угол не скруглён.

.message--washed {
    border-bottom-right-radius: 0;
    padding: 8px 12px 9px;
    margin-bottom: -17px;
}

А вот — стиль для текста, где настраивается свойство padding-bottom.

.message--washed__text {
    padding-bottom: 12px;
}

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 32

Процитированное сообщение

То же самое касается и цитат в виде изображений.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 33

Процитированное сообщение с изображением

Так же цитируются и вложения (вроде голосовых сообщений и видеозаписей).

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 34

Процитированное изображение с вложением

Реакции

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 35

«Реакция», выведенная под сообщением

Вот HTML-код:

<div class="message">
    <div class="message__outer">
        <div class="message__avatar"></div>
        <div class="message__inner">
            <div class="msg-bubble-wrapper">
                <div class="message__bubble"></div>
                <div class="reaction">
                    <div class="reaction__content">
                        <img src="assets/heart.png" alt="">
                        <img src="assets/smile.png" alt="">
                        <span class="count">2</span>
                    </div>
                </div>
            </div>
            <div class="message__actions"></div>
            <div class="message__spacer"></div>
        </div>
        <div class="message__status"></div>
    </div>
</div>

Элемент, который накладывается на другой элемент, это — содержимое «реакции», а не родительский элемент.

Вот стили:

.reaction__content {
    display: flex;
    align-items: center;
    background-color: #fff;
    border-radius: 10px;
    padding: 1px 1px 1px 3px;
    box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
    transform: translateY(-7px);
}

Кроме того, для того чтобы расположить элементы там, где находится начало родительского элемента, нужно выровнять их по Flexbox родительского уровня. Тут речь идёт о LTR-макетах, а в RTL-макетах всё делается с точностью до наоборот.

.msg-bubble-wrapper.with-reaction {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
}

Я заметил, что для того чтобы аватар оставался бы выровненным по нижней части сообщения, используется элемент <div>, высота которого равняется 18px, играющий роль разделителя.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 36

Размещение «реакций» в LTR и RTL-макетах

Вот разметка:

<div class="message__status">
    <img src="avatar.jpg" alt="">
    <div style="height: 18px;"></div>
</div>

Или, если речь идёт о сообщении отправителя, используется следующая разметка.

<div class="message__avatar">
    <img src="avatar.jpg" alt="">
    <div style="height: 18px;"></div>
</div>

Разметка списков сообщений

Разметка, используемая для оформления списков сообщений — это интересная тема. Тут имеется элемент <div> с role="grid" и с aria-label="Messages in conversation with Ahmad Shadeed".

В этом контейнере находятся элементы-строки <div> с role="row", а внутри каждого такого элемента имеется главный элемент с role="gridcell".

Вот разметка, о которой идёт речь.

<div class="mw_message_list" role="grid" aria-label="Messages in conversation with Ahmad Shadeed">
    <div role="row">
        <div role="gridcell" tabindex="0"></div>
    </div>
    <div role="row">
        <div role="gridcell" tabindex="0"></div>
    </div>
    <!-- other messages -->
</div>

Но это ещё не всё. В каждом сообщении имеется визуально скрытый элемент с текстом You sent или Person sent, в зависимости от того, кто отправил сообщение.

<div role="row">
    <div role="gridcell" tabindex="0">
        <h4 class="sr-only">You sent</h4>
        <!-- other elements -->
    </div>
</div>

Тут я обратил внимание на то, что одно из CSS-свойств, используемых для визуального скрытия текста — это clip-path: inset(50%).

Вот — полный код стилизации:

.sr-only {
    position: absolute;
    height: 1px;
    width: 1px;
    overflow: hidden;
    clip: rect(0px, 0px, 0px, 0px);
    clip-path: inset(50%);
}

Расстояние между сообщениями

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

Каждая строка с сообщением имеет элемент-разделитель высотой 2px или 7px, или, в некоторых случаях, и тот и другой. На следующем рисунке розовые элементы — это разделители высотой 7px, а жёлтые — это разделители высотой 2px.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 37

Элементы-разделители

Контейнер верхнего уровня каждого сообщения — это Flex-контейнер с flex-direction: column.

Вот, например, изображение процитированного сообщения. Тут имеются разделители обоих видов. Кроме того, используемый здесь родительский элемент верхнего уровня — это Flexbox-контейнер с flex-direction: column.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 38

Разделители двух видов

Разделители блоков сообщений, содержащие сведения о времени

Разделитель блоков сообщений может содержать текст или сведения о времени. Тут я расскажу о тех, которые содержат сведения о времени.

Когда пообщаешься с кем-то, а потом снова возвращаешься в чат — там можно заметить отцентрованный элемент со сведениями о дне недели и времени.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 39

Разделитель блоков сообщений

Для создания подобных элементов используется довольно-таки интересная разметка.

<h2><font color="#000">
    <div class="sr-only">October 28 at 6:12 PM</div>
    <div aria-hidden="true">Thu 6:12 PM</div>
</font></h2>

Первый элемент предназначен исключительно для средств чтения с экрана. Понять это можно благодаря классу sr-only (подсказка: дизайнеры Facebook пользуются вспомогательными классами, я добавил тут класс sr-only только ради понятности изложения). Второй элемент скрыт от средств для чтения с экрана, он предназначен для пользователя. Интересно — правда?

Пользовательская стилизация фона блоков сообщений

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 40

Градиентные цвета блоков сообщений

Когда пользователь прокручивает сообщения, всё выглядит так, будто их градиентная окраска анимирована.

Прокрутка списка сообщений с градиентной окраской

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

Градиент, который мы видели, устанавливается для фона элемента-контейнера, содержащего список сообщений. То есть — окрашивается весь тот белый фон на котором расположены сообщения. Взгляните на следующий рисунок.

.messages-parent {
    background-color: #3a12ff;
    background-image: linear-gradient(#faaf00, #ff2e2e, #3a12ff);
}

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 41

Градиентный фон

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

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 42

Настройка элементов, в которых выводятся сообщения

Далее — нужно задать белый фон буквально для всего, что имеется в интерфейсе — за исключением блоков, в которых выводятся сообщения (и это делается именно так).

.message__actions,
.message__status,
.spacer-xs,
.spacer-lg,
.message__spacer,
.message__avatar {
    background-color: #fff;        
}

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 43

Продолжение работы над градиентным фоном

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

Исследование интерфейса

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

Теперь, когда мы изолировали блоки сообщений, подумаем о том, как снова скруглить их углы. В этом деле нам помогут псевдоэлементы.

.custom-theming .message__bubble {
    position: relative;
    border-radius: 0;
    background: transparent;
}

.custom-theming .message__bubble::after {
    content: "";
    position: absolute;
    left: -36px;
    right: -36px;
    top: -36px;
    bottom: -36px;
    border: 36px solid #fff;
}

Этот ход позволяет нам выйти на следующий результат.

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 44

Продолжение стилизации блоков сообщений

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

внутренний радиус = border-radius - border-width

То есть — если даже добавить к вышеприведённым стилям border-radius: 36px, на угол это не повлияет. При использовании обычной стилизации, без градиента, свойство border-radius имеет значение 18px. Для того чтобы отразить это на внутреннем радиусе, нужно увеличить значение свойства border-radius псевдоэлемента.

внутренний радиус = = 54px - 36px = 18px

Вот отредактированные стили:

.custom-theming .message__bubble {
    position: relative;
    border-radius: 0;
    background: transparent;
}

.custom-theming .message__bubble::after {
    content: "";
    position: absolute;
    left: -36px;
    right: -36px;
    top: -36px;
    bottom: -36px;
    border-radius: 54px;
    border: 36px solid #fff;
}
Разработка настоящих компонентов: блок сообщения Facebook Messenger - 45

Скруглённые углы

И наконец — нужно заключить псевдоэлемент в пределы элемента-обёртки блока сообщения.

.custom-theming .message__bubble {
    position: relative;
    border-radius: 0;
    background: transparent;
    overflow: hidden;
}

Разработка настоящих компонентов: блок сообщения Facebook Messenger - 46

Ограничение псевдоэлемента рамками элемента-обёртки блока сообщения

Наш последний шаг заключается в изменении цвета границы (свойства border-color) на белый, после чего всё будет сделано как нужно.

Если вам интересно узнать о механизме анимации фона сообщений при прокрутке — знайте, что делается это посредством свойства background-attachment: fixed. И тут ещё используется граница элемента толщиной 2px белого цвета. Думаю, сделано так не без причины, а с прицелом на работу с пользовательским фоном.

.messages-parent {
    background-color: #3a12ff;
    background-image: linear-gradient(#faaf00, #ff2e2e, #3a12ff);
    background-attachment: fixed;
    border-left: 2px solid #fff;
    borfer-right: 2px solid #fff;
}

Итоги

Мне было интересно исследовать структуру блока сообщений Facebook Messenger. Мне очень понравился этот процесс. Вот мои основные выводы:

  • CSS Flexbox — это лучшее, что случилось с дизайном интерфейсов.
  • Не стоит судить о сложности разработки некоего компонента, глядя лишь на один из его вариантов.
  • Честно говоря — я не ожидал, что напишу об этом такую длинную статью. Когда я начал работу, я думал, что у меня получится небольшая заметка. Я ошибался — и мне это нравится.
  • Компромиссы — это неотъемлемая часть работы дизайнера. Например, всю пользовательскую стилизацию блоков сообщений можно реализовать с помощью JavaScript. Но команда разработчиков из Facebook решила сделать всё исключительно средствами CSS. Полагаю — из соображений производительности.
  • При работе над проектом, которым будут пользоваться жители разных стран, нужно учитывать то, что он должен поддерживать вывод текстов на разных языках, что эта возможность должна быть заложена в него с самого начала. Мне очень понравилось то, что Facebook Messenger — это именно такой проект, особенно учитывая то, что я написал руководство по RTL-стилизации.

«Препарируете» ли вы интерфейсы известных веб-проектов в поиске интересных идей?

Автор:
ru_vds

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js