company_banner

Интересные CSS-находки в дизайне Twitter

Автор оригинала: Ahmad Shadeed
  • Перевод
Хочу в очередной раз рассказать о результатах исследования дизайна сайта, который привлёк моё внимание. В прошлый раз я писал о CSS-механизмах, лежащих в основе нового дизайна Facebook. А теперь мне стало любопытно исследовать CSS-код Twitter. Новый дизайн Twitter появился почти год назад. В CSS-коде Twitter я нашёл много интересного: кое-что кажется мне просто замечательным, а кое-что — странным.



Соотношение сторон аватаров пользователей


Я обратил внимание на интересную реализацию аватара пользователя на странице профиля. В этой реализации используется CSS-техника сохранения соотношения сторон элемента.


Аватар пользователя на странице профиля

Ниже показан HTML- и CSS-код, иллюстрирующие реализацию аватара.

<a href="#" class="avatar">
    <div class="avatar-aspect-ratio"></div>
    <img alt="" src="me.jpg">
</a>
.avatar {
  position: relative;
  width: 25%;
  display: block;
}

.avatar-aspect-ratio {
  width: 100%;
  padding-bottom: 100%;
}

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

Техника сохранения соотношения сторон элемента работает благодаря тому, что, когда у элемента есть вертикальный внутренний отступ (свойство padding-bottom или padding-top), отступ зависит от ширины элемента. Взгляните на следующий пример:

.element {
    width: 250px;
    padding-bottom: 100%;
}

Вычисленное значение padding-bottom равняется 250px. Это означает, что у нас имеется идеальный квадрат. Команда Twitter использовала такую же технику, но в применении к элементу <img>, который, по отношению к родительскому элементу, позиционирован абсолютно. Почему? Вот причина.


С аватаром что-то не так

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

Теперь, когда мы разобрались с идеей, лежащей в основе этого решения, вернёмся к тому, как эта идея реализована в Twitter.

Свойство width: 25% основано на ширине элемента-обёртки. На моём экране это — 600px. Я задался вопросом о том, почему была выбрана именно такая методика. После того, как я лучше изучил CSS-код, я обратил внимание на то, что тот же самый компонент используется в модальном окне Edit Profile. Правда, элемент тут имеет меньший размер из-за использования следующего стиля:

.avatar {
    max-width: 8rem; /* 112px вместо 150px (25%) */
}

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


Управление шириной изображения

Здесь можно найти демонстрационный проект к этому разделу.

Верхний внешний отступ, заданный в процентах


Для того чтобы аватар пользователя перекрывал бы фото, находящееся в верхней части страницы профиля, используется отрицательный внешний отступ, заданный в процентах:

.avatar {
    margin-top: -18%;
}

Правда, в модальном окне Edit Profile для настройки верхнего внешнего отступа используется уже конструкция margin-top: -3rem. Обратите внимание на то, что отступ в модальном окне задаётся с использованием единиц измерения rem. Те же единицы измерения применяются и для настройки свойства max-width.

Странное использование CSS-функции calc()


Я обратил внимание на то, что для стилизации некоторых кнопок используется следующий CSS-код:

.button {
    min-width: calc(45.08px)
}

Зачем передавать функции calc() единственное значение? Не вижу в этом смысла. Может, число 45.08 хотели округлить? Но оно при таком подходе не округляется до 45. Ширина кнопки очень мала. Кнопка, при переводе приложения на RTL-язык, вроде арабского, окажется слишком маленькой.


Слишком маленькая кнопка

Функция calc() появляется во встроенном CSS-коде. Поэтому я полагаю, что это — результат динамической стилизации кнопки средствами React.

Смешивание CSS-фонов и HTML-изображений


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

<div style="background-image: url(me.jpg);"></div>
<img alt="" src="me.jpg">

Я видел такое в профиле пользователя, и в компоненте, используемом при формировании сеточного макета. Интересно то, что свойство opacity элемента <img> установлено в 0. Источником активного изображения является свойство background-image. Кроме того, тут используется и свойство background-size: cover, которое позволяет избежать искажения изображения.

Я попытался сделать всё наоборот, то есть — отобразить элемент <img> и скрыть CSS-фон, и сравнил сеточные макеты. Ниже показаны результаты этого сравнения.

Слева — вариант, в котором используется фоновое изображение, а справа — вариант, в котором применяется HTML-изображение

Очевидно то, что изображение справа искажено! Не знаю, почему команда разработчиков не воспользовалась CSS-свойством object-fit: cover для предотвращения искажений. И мне хотелось бы узнать о том, почему тут используются два изображения.

Сброс стилей


Я заметил один паттерн, который заключается в постоянном использовании CSS-класса, который добавляется к элементам <div>. Вот соответствующий CSS-код:

.css-1dbjc4n {
    align-items: stretch;
    border: 0 solid black;
    box-sizing: border-box;
    display: flex;
    flex-basis: auto;
    flex-direction: column;
    flex-shrink: 0;
    margin-bottom: 0px;
    margin-left: 0px;
    margin-right: 0px;
    margin-top: 0px;
    min-height: 0px;
    min-width: 0px;
    padding-bottom: 0px;
    padding-left: 0px;
    padding-right: 0px;
    padding-top: 0px;
    position: relative;
    z-index: 0;
}

Этот стиль применяется буквально к каждому элементу <div>, находящемуся на странице. Почему? Разве тут недостаточно сброса CSS-стилей?

Некоторые из вышеописанных стилей мне вполне понятны, вроде min-width: 0, так как тут прослеживается некоторая связь с Flexbox. Но как насчёт внутренних отступов, внешних отступов, границ? Почему у некоего элемента <div> надо сбрасывать стили, учитывая то, что у этого элемента нет соответствующих свойств?

Flexbox и min-width: 0


Значением по умолчанию свойства min-width является auto, которое оказывается равным нулю. Когда нечто выводится как flex-элемент, значение его свойства min-width равно размеру его содержимого. Позволение подобного поведения может нарушить макет в том случае, если содержимое элемента больше, чем он сам.

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


Flex-элемент и Flexbox-обёртка

Макет, приведённый выше, показывает, что может произойти в том случае, если содержимое flex-элемента окажется слишком длинным. Обратите внимание на то, что текст выходит за пределы элемента-обёртки. А вот что получается при использовании свойства min-width: 0.


Содержимое не выходит за пределы flex-элемента

Использование свойства position: sticky


Я заметил использование свойства position: sticky в правой боковой панели Twitter (там, где выводятся медиа-тренды и рекомендации, касающиеся пользователей, на которых можно подписаться). Мне показалось интересным то, как организована работа со значениями свойств bottom и top при скроллинге. Значения, установленные по умолчанию, выглядят так:

.sidebar {
    position: sticky;
    width: 350px;
    bottom: -470.5px;
}

Когда страницу прокручивают вниз, свойство bottom заменяется на свойство top: -480.5px. Я думаю, что дизайнеры поступили так для того чтобы позволить пользователю сначала дойти до конца боковой панели. Свойство top добавляется уже после этого.

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


Так же, как и в проанализированном мной ранее дизайне Facebook, в дизайне Twitter, во многих местах, используются элементы-разделители. Всё это — flex-элементы, ширина которых установлена с использованием свойства flex-basis.


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

В первом элементе, приведённом на предыдущем изображении, при настройке элемента-разделителя используется свойство flex-grow: 1. Во втором используется фиксированная ширина, заданная в пикселях.

Оформление содержимого твитов



Твит

Выше показан созданный мной твит. На первый взгляд может показаться, что перед нами — элемент <p> или <span>, в который добавлен текст. Но, как оказалось, каждый смайлик представлен отдельным тегом <span>, а если текст находится между двумя такими элементами, он тоже оформляется в виде тега <span>.


Оформление текста твита

Смайлик — это <span>, в котором есть элемент <div>, в <div> содержатся два изображения. Одно — это CSS-фон, второе — HTML-изображение.

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


Второе предложение

Использование вычисляемых значений для разделения элементов



Кнопка Назад

При выполнении поиска в Twitter, или при открытии страницы профиля, можно видеть кнопку Назад. К этой кнопке применён стиль margin-left: -4px, задающий отрицательный внешний левый отступ. Меня заинтересовала процедура вычисления этого значения.

.back-button {
    margin-left: calc(5px + (-1 * (39px - 1.5em)) / 2);
}

Вышеприведённые вычисления приводят к получению значения -4px. Почему бы не задать просто -4px? Не лучше ли это в данном случае, чем применение функции calc()?

Ширина навигационных ссылок



Навигационные ссылки

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

Здесь моё внимание привлекло использование свойства flex-direction: column для каждого элемента навигационной панели. Почему? Нужно ли это в ситуации, когда тут используется лишь один дочерний элемент?

Внешний отступ, добавляемый «на всякий случай»


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


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

Обратите внимание на то, что происходит в том случае, когда содержимое элемента заметно длиннее, чем его обычное содержимое. А именно, тут происходит следующее:

  1. Текст обрезается.
  2. Между элементами присутствует внешний отступ.

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

Модальные окна в областях просмотра маленькой высоты


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


Недоступная кнопка Save

Так как модальное окно не поддерживает прокрутку, до кнопки Save мне добраться не удалось. А вот окно, используемое для управления внешним видом элементов, поддерживает динамическое изменение высоты и скроллинг.


Окно, поддерживающее динамическое изменение высоты

Почему одно окно поддерживает скроллинг, а другое — нет? Интересно знать, какое из них, по мнению дизайнеров Twitter, важнее? Как по мне — так это именно то окно, которое используется для редактирования профиля.

А вы планируете ли вы взять на вооружение какие-нибудь идеи из дизайна Twitter?

RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR

Комментарии 11

    +1
    margin-left: calc(5px + (-1 * (39px — 1.5em)) / 2);
    Вышеприведённые вычисления приводят к получению значения -4px. Почему бы не задать просто -4px? Не лучше ли это в данном случае, чем применение функции calc()?

    Тут вы упускаете, что em размер относительный поэтому и сам отступ в таком случае потенциально динамический.

    Не знаю, почему команда разработчиков не воспользовалась CSS-свойством object-fit: cover для предотвращения искажений.

    У background-size: cover поддержка значительно выше тут и думать не о чем. Решение с невидимыми изображениями интересное, надо взять на заметку. Интересно как к этому поисковики относятся.

    Техника сохранения соотношения сторон элемента работает благодаря тому, что, когда у элемента есть вертикальный внутренний отступ (свойство padding-bottom или padding-top), отступ зависит от ширины элемента.

    Эта древняя техника наверное появилась одновременно с padding, но встречается редко

    Зачем передавать функции calc() единственное значение?

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

    Я заметил использование свойства position: sticky в правой боковой панели Twitter

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

    ХаХ. Только сейчас понял, что это перевод и отвечаю я не автору))) Но все равно спасибо, интересные наблюдения.
      0
      Вот это смущает, поддержка ужасная, как по мне решения на js более стабильные, но наверно они что-то знают.
      они знают что поддержка есть во всех современных браузерах. а для 1.47% пользователей можно и через js сделать
        –3
        Может вы не заметили, но даже у Chrome, которым пользуется подавляющее большинство, поддержка частичная (~ Partial support). Полная поддержка с учетом префиксов только 26.22%. У вас не возникает диссонанса, когда применяется position: sticky в то время как отказываются от object-fit: cover у которого действительно есть поддержка?
          0
          Может вы не заметили

          Я не заметил. Там же указано в каком имено случае не работает.

            –1
            homm правильно указал. поддержка не 26.22%, а все 94.41%

            а object-fit: cover не применяют скорее всего потому что руки еще не дошли выпилить весь говнокод и заменить на современные методы
              –2
              Полная поддержка с учетом префиксов только 26.22%.
              +68.19% частичной поддержки

              а object-fit: cover не применяют скорее всего потому что руки еще не дошли выпилить весь говнокод и заменить на современные методы
              Это новый дизайн и новая верстка, то есть они подумали и решили напишем-ка мы говнокод, а потом заменим на «современные методы»? Ок…
        0

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

          0

          С фолбеком, кстати, у твиттера очень красиво в отличие от многих. Даже из elinks вполне юзабелен.

          0

          О, как кстати. Просто оставлю это здесь:


            0
            Вот с этим calc() есть забавный баг. Картинки лайков, реплаев и ретвитов рисуются svg в квадрате со стороной в 1.25em = 17.5px.
            .r-1hdv0qi {
                width: 1.25em;
            }
            

            А при их активации и появлении цифры счётчика его высота уже вычисляется
            .r-1o9f8vr {
                line-height: calc(18.375px);
            }
            

            , что округляется до 18px, из-за чего происходит смещение ленты на эти полпикселя, а там ещё и анимация, поэтому переход прям заметно. Уже несколько месяцев это наблюдаю, до сих пор не исправили, написать им что ли.
              0
              А какой параметр задали в итоге для навигационных ссылок? Могли бы вы поделиться вашим CSS исходником?

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

              Самое читаемое