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

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

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

image-loader.svg

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

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


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

6c025d695b32350480accc261ecf94db.jpg


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

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

45bd0c557d4623acd721e558e1ed3459.jpg


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

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

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

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


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

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

f70702a605ad0f3ceda1984a5d5488e6.jpg


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

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

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

4078e2bd3ea9387a8fd20fba938cf309.jpg


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

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

b4f6121d068de8e74d9ca6c39bba1bb9.jpg


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

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

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


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

0e1880f0f9df18c5e6aee24f482696fc.jpg


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

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

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

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

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


0247c76938c3452103bb2dd60edb35c7.jpg


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

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

446e45d28daed66111b188741f9790fc.jpg


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

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

57f19659f7531331c4d19fa04c8fdef2.jpg


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

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

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


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

    
        
        
            
            
            
        
        
    


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

.message__outer {
    display: flex;
}

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


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

c1a5e7ebdd1bb278e8f3568472dae7ab.jpg


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

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

Вот CSS-код:

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

.message__spacer {
    flex: 1;
}


7ab9e8cb3296926b8633af53b59cec51.jpg


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

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

  • Резервирование пространства для предотвращения сдвига макета.
  • Улучшение возможностей управления внутренней частью макета. Например, при стилизации области вывода сообщения нужно использовать свойство 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.


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

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

c004ddf0703bd51c5d6a526489349872.jpg


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

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


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

4cf2295b40250664b13665b4b9f13fa3.jpg


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

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

7afef56eb15dd5c5fa52919237047b55.jpg


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

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

    


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

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


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

426ce094d16f7f6658e0b2c1b5e3e19d.jpg


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

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

797758b1c0434959d11b6d3cd286386f.jpg


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

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


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

Вот HTML-код:

    Photo of Ahmad Shadeed


Вот — стили:

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


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

14fa83f910aa18654698625f1e8813f8.jpg


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

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

07da876695d154921902424ad3275b58.jpg


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

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

28a214c386581dd8167822cf668a7f60.jpg


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

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


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

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


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

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


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

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

be67257bde835fbe86094f32816467fe.jpg


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

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

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

    


Вот CSS-код:

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


de86f04f3a357954543527cb52321f61.jpg


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

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

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

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


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

352bdd7610f5384a36e849bd3ef2e46b.jpg


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.

08f740c922da9dd0c5233dd8f2526ab4.jpg


LTR-макет

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

2ae3598ce0c8e1ca52ac3b515873d78b.jpg


RTL-макет

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

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


921799fe2d6f56b98a84fe9926472269.jpg


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

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

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

Вот HTML-код:


    
        
            
                
                                     
            
        
    


Вот — стили:

.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.
  • Соотношение сторон изображения не должно меняться при изменении его размеров.
  • Если размеры изображения меньше вышеозначенных значений — оно должно выводиться в своём исходном состоянии.


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

374574e9f77fac223e6934bcd7cce5dc.jpg


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

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

390842ae6e047ce87f0f8b969b60f9f4.jpg


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

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

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


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

612d916b2063b564b2a10bb4ea21c4f4.jpg


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

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

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


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

54acb44554e75941a0bf9bea5736192a.jpg


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

.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 — взгляните на эту мою статью.

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


ac08e3c14a0e3f5336f9c41a7a5c8e38.jpg


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

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

  • Заголовок, показывающий, кто кому ответил (например — «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;
}


6aed02ae2636965fe3d78f2098c2cc01.jpg


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

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

e2053c16bb2e817e1c11d0a7658c029a.jpg


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

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

4d2c59f359c0e9cecea00034218c46e9.jpg


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

Реакции


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

700710b8a8eb1946c9cf460cad4f6a2a.jpg


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

Вот HTML-код:

    
        
        
            
                
                
                    
                                                                          2                     
                
            
            
            
        
        
    


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

Вот стили:

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


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

, высота которого равняется 18px, играющий роль разделителя.

f5997ba020556331f027b10103a3b906.jpg


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

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

         


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

         


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


Разметка, используемая для оформления списков сообщений — это интересная тема. Тут имеется элемент

с role="grid" и с aria-label="Messages in conversation with Ahmad Shadeed".

В этом контейнере находятся элементы-строки

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

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

    
        
    
    
        
    
    


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

    
        

You sent

             


Тут я обратил внимание на то, что одно из 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.

e3973456076aa56ab9180b255a8c874c.jpg


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

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

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

a2bfbda0f9289501516f7ec6257087d5.jpg


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

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


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

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

1d51f5f8521768d7806f24a7b24c84cd.jpg


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

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

    
October 28 at 6:12 PM
    


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

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


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

cf3e351dc91734c196c4eb7508054199.jpg


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

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


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

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

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

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


6f877c72c554f3e7f20a52534bfd8ce0.jpg


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

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

fd99c17fc3c044cb89d7275dd14e9cc9.jpg


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

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

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


1262a4c01b33c3f69ac6d9518aaff5b5.jpg


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

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


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

e0f340e602c2425fec21feb4030a8bd9.jpg


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

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

внутренний радиус = 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;
}
931548587c7a0e9a7ea498c83e6737c1.jpg


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

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

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


918250280848a37e75ebf01d2d04697e.jpg


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

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

Е

© Habrahabr.ru