Верстка: отображаем пользовательский контент

в 9:14, , рубрики: css, html, Блог компании ДоксВижн, верстка, верстка сайтов, Разработка веб-сайтов

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

Верстка: отображаем пользовательский контент - 1


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

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

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

Прежде всего, следует сказать, что, если где-то на странице присутствует пользовательский текст, то нужно обязательно предусмотреть случай, когда он будет слишком длинным. Здесь, как правило, все решается одним из двух способов:

1. Обрезанием лишнего текста с помощью многоточия,
2. Переносом на новую строку (здесь часто бывает желательно сохранение оригинального форматирования текста — пробелов и переносов строк).

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

Однострочный текст

Верстка: отображаем пользовательский контент - 2

Заставить работать многоточие просто, как раз-два-три:

1. Ограничить ширину блока (см. далее в этой статье);
2. Задать для этого блока (именно для него) правила:

overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
// direction: rtl; ?

3. Элемент, содержащий текст внутри блока (если текст во вложенных элементах), а также все его родители, вплоть до блока, должны иметь display: inline. Помимо текста в блоке могут быть и другие не inline-элементы, важно только, чтобы сам текст и его родители были inline.

Если многоточие не работает, то алгоритм отладки прост:

1. В DevTools смотрим ширину текста, и ширину всех его родителей по очереди;
2. Находим элемент, который должен ограничивать ширину, и, если он «расползся», то заставляем его вернуться в рамки;
3. Проверяем, что в нем заданы все CSS-свойства, приведенные выше;
4. Проверяем, что все элементы от блока до текста имеют display: inline.

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

Верстка: отображаем пользовательский контент - 3

Для этого нужно программно определить направление и задать соответствующее значение свойства direction в css, либо значение HTML-атрибута dir. В библиотеке twitter RTLtextarea, например, направление текста определяется примерно так:

// Полностью см. https://github.com/twitter/RTLtextarea/blob/master/src/RTLText.module.js
function getDirection(plainText) {
    var rtlChar = /[u0590-u083F]|[u08A0-u08FF]|[uFB1D-uFDFF]|[uFE70-uFEFF]/mg;
    return plainText.match(rtlChar) ? "rtl" : "ltr";
}

Логику задания направления текста удобно вынести в директиву в случае использования AngularJS или в stateless компонент в случае с ReactJS.

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

Верстка: отображаем пользовательский контент - 4

Либо дать пользователю возможность каким-то еще способом увидеть весь текст.

Многострочный текст

Верстка: отображаем пользовательский контент - 5

Для варианта с переносом строк основную проблему составляют длинные слова и сохранение форматирования. Готовя материал к данной статье, я решил посмотреть, как она решается на передовых сайтах, ориентированных на пользовательский контент. Сюда вошли facebook, twitter, vk, linkedin, livejournal, одноклассники, g+, youtube, яндекс маркет, instagram, системы комментирования hyper comments, disqus, intense debate, cackle, livefyre, ну и конечно же, habr.

Ресурс Длинные слова Переносы строк Пробелы
habr (комментарии) overflow: hidden препроцессинг теряются
facebook (посты и комментарии) word-wrap: break-word препроцессинг теряются
twitter (твиты) word-wrap: break-word white-space: pre-wrap white-space: pre-wrap
vk (посты и комментарии) word-wrap: break-word препроцессинг теряются
linkedin (посты и комментарии) word-wrap: break-word white-space: pre-line теряются
livejournal (посты) препроцессинг (<wbr>) html html
livejournal (комментарии) препроцессинг (<wbr>) препроцессинг теряются
одноклассники (заметки) word-wrap: break-word white-space: pre-wrap white-space: pre-wrap
одноклассники (комментарии) word-wrap: break-word препроцессинг + white-space: pre-wrap (?) white-space: pre-wrap
g+ (посты и комментарии) word-wrap: break-word препроцессинг препроцессинг
youtube word-wrap: break-word white-space: pre-wrap white-space: pre-wrap
яндекс-маркет (обсуждения) bug (растягивается за пределы) препроцессинг теряются
яндекс-маркет (отзывы) word-wrap: break-word препроцессинг теряются
instagram (комментарии) word-wrap: break-word ограничение ввода теряются
hyper comments word-wrap: break-word препроцессинг теряются
disqus overflow: hidden препроцессинг теряются
intense debate overflow: hidden препроцессинг теряются
cackle word-wrap: break-word white-space: pre-wrap white-space: pre-wrap
livefyre word-wrap: break-word препроцессинг препроцессинг

Когда я закончил составлять эту таблицу, у меня было только одно впечатление:

Верстка: отображаем пользовательский контент - 6

Как видно, почти везде используется препроцессинг (программная замены переносов строк на тэги <br>, пробелов на &nbsp; и разбитие длинных слов при помощи <wbr>). При этом часто пробелы в пользовательских сообщениях попросту теряются. Это довольно печально, учитывая тот факт, что есть простое и эффективное решение проблемы, состоящее в: a) ограничении ширины блока и б) добавлении всего двух правил:

word-wrap: break-word;
white-space: pre-wrap;

Либо, если вы предпочитаете не разрывать длинные слова на несколько строк, а обрезать многоточием:

overflow: hidden;
text-overflow: ellipsis;
white-space: pre-wrap;

Либо, если вы все-таки специально хотите убрать форматирование текста:

word-wrap: break-word;
white-space: normal;

Это работает во всех браузерах вплоть до IE8 и красиво решает проблему длинных слов и сохранения форматирования. Тем не менее, почему-то только twitter, одноклассники, youtube и cackle прибегли к этому способу. Маяковский просто переворачивается в гробу, когда, например, пытаешься запостить в vk что-то вроде:

Для веселия
           планета наша
                       мало оборудована.
Надо
    вырвать
           радость
                  у грядущих дней.
В этой жизни
            помереть
                    не трудно.
Сделать жизнь
             значительно трудней.

Маяковский — Сергею Есенину (отрывок)

Искренне надеюсь, что команда vk увидит эту статью и заменит препроцессинг на вот такой кусочек кода в своих стилях (word-wrap: break-word они уже используют):

.wall_post_text {
    white-space: pre-wrap;
}

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

Ограничение ширины элемента

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

Верстка: отображаем пользовательский контент - 7

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

Борьба с расползанием элементов специфична для каждого вида элемента (block, inline-block и т.д.). Поэтому при отладке таких случаев необходимо пробежаться вверх по родителям проблемного элемента и найти «слабое звено». Рассмотрим каждый из видов элементов отдельно.

1) display: block;

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

2) display: inline-block;

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

max-width: 100%;

никогда не помешает. Это довольно безопасно и обычно ничего не ломает. Но при этом дает вам гарантию, что inline-block всегда будет в пределах своего родителя.

3) float

Когда элемент подвешивается на float, то тут даже block теряет всю свою уверенность и уподобляется слабовольному inline-block. Задание width или max-width возвращает его правоверность, и также max-width: 100% здесь будет удачной идеей. Это вообще настолько хорошее правило, что значение 100% я бы сделал значением по умолчанию для свойства max-width. В конце концов, додумались же сделать по умолчанию min-width: auto в спецификации flexbox, что куда более опасно. Но об этом позже.

Изменение поведения block при float — все, что нужно знать о float. Остальные элементы и без того требуют ограничения своей ширины, и для них ничего не меняется.

4) display: table;

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

display: table;
table-layout: fixed;

Оно сразу вразумляет таблицу, и простой width или max-width решают все проблемы с шириной.

5) display: flex;

Flexbox неимоверно крут, и позволяет делать простым способом то, что иначе очень трудно, либо вообще невозможно. Однако использовать его для отображения пользовательского контента пока не самая лучшая идея. По крайней мере, если вы намерены корректно обрабатывать длинные слова и поддерживать IE10.

Если не брать во внимание IE10, то все замечательно. Нужно только знать пару особенностей и правильно настроить flexbox. Контейнер flexbox (там, где задано display: flex) нас в данном случае не интересует, т.к. с ним проблем не возникает. Проблемы возникают с элементами контейнера, и тут нужно соблюсти следующие правила:

1. Во-первых, shrink-factor (второй параметр свойства flex) должен быть больше нуля, чтобы браузер взялся за сжатие нашего элемента.

2. Во-вторых, необходимо сбросить в 0 пресловутое свойство min-width. Его значение auto было введено специально для flexbox, и к тому же сделано значением по умолчанию. Лично для меня это всегда было источником непредсказуемого контр интуитивного поведения flexbox, пока я об этом не знал. И, что особенно печально, об этом нигде не пишут. Ни в одном «полном руководстве» по flexbox ни слова не сказано про эту ловушку. Я в свое время узнал об этом случайно, попав через поиск на багтрекер Firefox, где разработчики, объясняя поведение браузера, сослались на спецификацию (Firefox реализовал эту часть спецификации раньше Chrome, поэтому некоторые считали это багом Firefox, т.к. из-за «min-width: auto» flexbox ведет себя не так, как ожидаешь).

3. Вроде бы, первых двух правил должно было быть достаточно, но на практике, чтобы сжать длинные слова, браузерам требуется задать также flex-basis (третий параметр свойства flex), отличный от auto. Соответственно при этом важно, чтобы grow-factor (первый параметр свойства flex) был больше нуля, т.к. иначе наш элемент сожмется до значения flex-basis (например, до нуля, если flex-basis: 0).

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

flex: 1 1 0px;
min-width: 0;

Обратите внимание, что третье значение в свойстве flex задано с единицами измерения. Это баг в IE11, и без единиц измерения в нем ничего не работает. Вместо нуля можно, конечно, задать и что-то другое (кроме auto), а вместо единиц для grow- и shrink-факторов что угодно, кроме 0.

Однако, в IE10, к сожалению, это не работает — длинные слова беспощадно растягивают элементы, несмотря ни на что. Можно, конечно, задать width или max-width, но в случае с flexbox, как правило, этого сделать нельзя, т.к. ширина элемента неизвестна заранее (либо это уже будет не flexbox). Например, простой макет: иконка и текст справа. Иконка занимает определенную ширину, а текст — все оставшееся пространство контейнера. Если у вас получится заставить этот пример отображаться в IE10 средствами flexbox так же, как в IE11, напишите, пожалуйста, в комментариях. Проверить можно здесь.

На этом все, если что-то упустил, пишите в комментариях. Приятной вам верстки!

P.S. отдельная благодарность Вадиму Макееву, руководителю и редактору портала Веб-стандарты за техническое рецензирование статьи.

Автор: ДоксВижн

Источник


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


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