Pull to refresh

text-overflow в Firefox и все, все, все

Reading time 9 min
Views 14K
Многие наверняка сталкивались с проблемой, когда какой-нибудь текст нужно выводить в одну строку. При этом текст может быть весьма длинным, а ширина блока, в котором этот текст находится, обычно ограничена, хотя бы тем же размером окна браузера. На эти случаи придумано свойство text-overflow, которое внесено в рекомендацию CSS3, а впервые было реализовано в IE6, очень давно. В случае использования этого свойства для блока, если его текст больше по ширине чем сам блок, то текст обрезается и в конце ставится многоточие. Хотя тут не все так просто, но вернемся к этому чуть позже.
С Internet Explorer'ом все понятно, что же относительно других браузеров? И хотя в настоящий момент из спецификации CSS3 свойство text-overflow исключено, Safari его поддерживает (по крайней мере, в 3-й версии), Opera тоже (с 9-й версии, правда называется свойство -o-overflow-text). А Firefox — нет, не поддерживает, и даже в 3-й версии не будет. Печально, но факт. Но может можно что-то сделать?



Сделать, конечно, можно. Когда искал в интернете по поводу этого свойства, и как с этим в Firefox, наткнулся на статью с простым решением. Суть решения:
  1. Ограничиваем блок по ширине через max-width или просто width.
  2. С помощью ::after создаем автогенерируемый контент, который содержит многоточие (три точки).

Вот и все. Детали читайте в статье.
Решение не плохое, но есть проблемы:
  1. Текст может обрезаться посередине (условно говоря) буквы, и мы увидим ее «обрубок».
  2. Многоточие отображается всегда, даже когда текст меньше ширины блока (то есть не выпадает из него и многоточие не нужно).


Шаг первый



Для начала сосредоточимся на второй проблеме, а именно, как избежать отображения многоточия когда это не нужно. Поломав голову и «немного» поэкспериментировав, нашел некоторое решение. Попробую объяснить.
Суть в том, что нам нужен отдельный блок с многоточием, который будет появляться только тогда, когда текст занимает слишком много пространства по ширине. Тут мне пришла идея о сваливающемся плавающем блоке. Хоть и звучит страшно, но тут, всего лишь, имеется ввиду блок, который есть всегда, и прижат вправо, но когда ширина текста становится большой, текст выталкивает блок на следующую линию.
Перейдем к практике, иначе сложно объяснить. Зададим HTML структуру:
    <div class="ellipsis">
      <div>very long text</div>
      <div></div>
    </div>

Не очень компактно, но меньшего у меня не получилось. Итак, мы имеем блок-контейнер DIV.ellipsis, блок с нашим текстом и еще один блок, который будет содержать многоточие. Заметим, что «блок с многоточием» на самом деле пустой, ведь нам не нужны лишние три точки, когда мы будем копировать текст. Так же не стоит пугаться отсутствия дополнительных классов, так как данная структура хорошо адресуется посредством CSS селекторов. А вот и сам CSS:
    .ellipsis
    {
      overflow: hidden;
      white-space: nowrap;
      line-height: 1.2em;
      height: 1.2em;
      border: 1px solid red;
    }

    .ellipsis > DIV:first-child
    {
      float: left;
    }
    .ellipsis > DIV + DIV
    {
      float: right;
      margin-top: -1.2em;
    }
    .ellipsis > DIV + DIV::after
    {
      background-color: white;
      content: '...';
    }

Вот и все. Проверяем и убеждаемся что в Firefox, Opera, Safari работает как и задумано. В IE весьма странный, но предсказуемый, результат. В IE6 все разъехалось, а в IE7 просто не работает, так как он не поддерживает генерируемый контент. Но к IE мы еще вернемся.

Пока же разберем сделанное. Во-первых, мы задаем line-height и height основного блока, так как нам нужно знать высоту блока и высоту текстовой линии. Это же значение мы задаем для margin-top блока с многоточием, но с отрицательным значением. Таким образом, когда блок не «сброшен» на следующую линию, то будет выше строки текста (на одну линию), когда сбросится — будет на ее уровне (на самом деле он ниже, просто мы делаем оттяжку на одну линию вверх). Что бы скрыть лишнее, особенно когда не нужно показывать многоточие, мы делаем overflow: hidden для основного блока, таким образом, когда многоточие будет выше линии — оно не будет показываться. Это же позволяет нам убрать и, выпадающий за пределы блока (в правый край), текст. Чтобы текст неожиданно не переносился и не выталкивал блок с многоточием все ниже и ниже, мы делаем white-space: nowrap, тем самым запрещая переносы — наш текст будет всегда в одну строку. Для блока с текстом мы поставили float: left, чтобы он сразу же не сваливал блок с многоточием и занимал минимальную ширину. Так как внутри основного блока (DIV.ellipsis) оба блока плавающие (float: left/right), то основной блок схлопнется, когда блок с текстом будет пустой, поэтому для основного блока мы выставили фиксированную высоту (height: 1.2em). Ну и последнее, используем псевдо-элемент ::after для отображения многоточия. Для этого псевдо-элемента так же задаем фон, чтобы перекрыть текст который окажется под ним. Мы задали рамку для основного блока, только для того чтобы увидеть габариты блока, позже мы ее уберем.
Если бы Firefox, так же хорошо поддерживал псевдо-элементы, как Opera и Safari, в плане их позиционирования (задания для них position/float etc), то можно было бы не использовать отдельный блок для многоточия. Попробуйте заменить последние 3 правила, на следующий:
    .ellipsis > DIV:first-child::after
    {
      float: right;
      content: '...';
      margin-top: -1.2em;
      background-color: white;
      position: relative;
    }

Посмотрите в Opera и Safari, все работает как прежде, и без дополнительного блока с многоточием. А вот Firefox разочаровывает. А ведь именно для него мы делаем решение. Что ж — придется обходиться изначальной HTML структурой.

Шаг второй



Как вы могли заметить, мы избавились от проблемы появления многоточия, когда текст умещается в блок. Однако, у нас осталась еще одна проблема — текст обрезается посередине букв. И к тому же в IE это не работает. Чтобы побороть и то и другое, нужно использовать родное правило text-overflow для браузеров, и только для Firefox использовать описанное выше решение (альтернативы нет). Как сделать решение «только для Firefox» разберемся позже, а сейчас попробуем заставить работать то что есть с использованием text-overflow. Подправим CSS:
    .ellipsis
    {
      overflow: hidden;
      white-space: nowrap;
      line-height: 1.2em;
      height: 1.2em;
      border: 1px solid red;
      text-overflow: ellipsis;
      -o-text-overflow: ellipsis;
      width: 100%;
    }
    .ellipsis *
    {
      display: inline;
    }

    /*
    .ellipsis > DIV:first-child
    {
      float: left;
    }
    .ellipsis > DIV + DIV
    {
      float: right;
      margin-top: -1.2em;
    }
    .ellipsis > DIV + DIV::after
    {
      background-color: white;
      content: '...';
    }
    */

Править, как оказалось, не много. В стиль основного блока добавилось три строчки. Две из них включают text-overflow. Задание ширины width: 100% нужно для IE, чтобы текст не раздвигал блок до бесконечности, и свойство text-overflow сработало; по сути, мы ограничили ширину. По идее DIV, как и все блочные элементы, растягивается по всей ширине контейнера, и width: 100% ни к чему, и даже вредно, но у IE проблема с layout, так как контейнер всегда растягивается по размерам контента, поэтому иначе нельзя. Так же мы сделали все внутренние элементы строковыми (inline), потому как для некоторых браузеров (Safari & Opera) text-overflow иначе не сработает, так как внутри есть блочные (block) элементы. Мы закомментировали три последних правила, так как в данном случае они не нужны и все ломают (конфликтуют). Данные правила нужны будут только для Firefox.
Посмотрим что у нас получилось и продолжим.


Шаг третий



Когда я в очередной раз заглянул на страничку (перед тем как собирался писать эту статью), упоминаемую в самом начале, то, интереса ради, проглядел комментарии, на предмет умных смежных идей. И нашел интересную ссылку, в комментарии, где говорилось о другом решении, которое работает в Firefox и IE (для этого человека, как и для автора первой статьи, других браузеров, видимо, не существует). Так вот, в этом решении, автор несколько иначе борется с данным явлением (отсутствием text-overflow), навешивая обработчики на события overflow и underflow элементам, для которых нужно было ставить многоточие при необходимости. Не плохо, но мне кажется это решение очень дорогое (в плане ресурсов), тем более что оно у него несколько подглючивает. Однако, разбираясь, как он этого добился, наткнулся на интересную штуку, а именно CSS свойство -moz-binding. Насколько я понял, это аналог behaviour в IE, только под другим соусом и покруче. Но не будем углубляться в детали, скажем только, что таким способом можно повесить JavaScript обработчик на элемент с помощью CSS. Звучит странно, но это работает. Что мы делаем:
    .ellipsis
    {
      overflow: hidden;
      white-space: nowrap;
      line-height: 1.2em;
      height: 1.2em;
      border: 1px solid red;
      text-overflow: ellipsis;
      -o-text-overflow: ellipsis;
      width: 100%;
      -moz-binding: url(moz_fix.xml#ellipsis);
      zoom: 1;
    }
    .ellipsis *
    {
      display: inline;
    }
    
    .moz-ellipsis > DIV:first-child
    {
      float: left;
      display: block;
    }
    .moz-ellipsis > DIV + DIV
    {
      float: right;
      margin-top: -1.2em;
      display: block;
    }
    .moz-ellipsis > DIV + DIV::after
    {
      background-color: white;
      content: '...';
    }

Как видно мы опять внесли не много изменений. На этом шаге в IE7 наблюдается странный глюк, все перекашивается, если не поставить zoom: 1 для основного блока (самый простой вариант). Если убрать (удалить, закомментировать) правило .ellipsis * или .moz-ellipsis > DIV + DIV (которое вообще никак не касается IE7), то глюк пропадает. Странно все это, если кто знает в чем дело, дайте знать. Пока же обойдемся zoom: 1 и перейдем к Firefox.
Свойство -moz-binding подключает файл moz_fix.xml инструкцию с идентификатором ellipsis. Содержимое этого xml файла следующее:
<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">

<binding id="ellipsis" applyauthorstyles="false">
	<implementation>
		<constructor><![CDATA[
			this.style.mozBinding = '';
			this.className = this.className + ' moz-ellipsis';
		]]></constructor>
	</implementation>
</binding>

</bindings>

Все что делает данный constructor, это к элементу, для которого сработал селектор, добавляет класс moz-ellipsis. Это будет работать только в Firefox (gecko браузерах?), поэтому только в нем к элементам будет добавлен класс moz-ellipsis, и мы можем для этого класса дописать дополнительные правила. Чего и добивались. Не совсем уверен относительно необходимости this.style.mozBinding = '', но по опыту с expression лучше перестраховаться (вообще я слабо знаком с этой стороной Firefox, потому могу заблуждаться).
Вас может насторожить, что в данном приеме используется внешний файл и вообще JavaScript. Пугаться не стоит. Во первых если файл не подгрузится и/или JavaScript отключен и не сработает, ничего страшного, пользователь просто не увидит многоточия в конце, текст будет обрезаться по окончанию блока. То есть в данном случае получаем «unobtrusive» решение. Можете сами убедиться.

Таким образом, мы получили стиль для браузеров «большой четверки», который реализует text-overflow для Opera, Safari & IE, а для Firefox его эмулирует, не ахти как, но это лучше чем ничего.

Шаг четвертый



На этом месте можно было бы поставить точку, но хотелось бы немного улучшить наше решение. Раз мы можем повесить constructor на любой блок и соответственно получаем над ним контроль, почему бы не воспользоваться этим. Упростим нашу структуру:
    <div class="ellipsis">very long text</div>

О, да! Думаю, вы со мной согласитесь — это то что надо!
Теперь уберем из стиля все лишнее:
    .ellipsis
    {
      overflow: hidden;
      white-space: nowrap;
      line-height: 1.2em;
      height: 1.2em;
      text-overflow: ellipsis;
      -o-text-overflow: ellipsis;
      width: 100%;
      -moz-binding: url(moz_fix.xml#ellipsis);
    }

    .moz-ellipsis > DIV:first-child
    {
      float: left;
    }
    .moz-ellipsis > DIV + DIV
    {
      float: right;
      margin-top: -1.2em;
    }
    .moz-ellipsis > DIV + DIV::after
    {
      background-color: white;
      content: '...';
    }

Мы наконец-то убрали красную рамку :)
А теперь, немного допишем наш moz_fix.xml:
<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">

<binding id="ellipsis" applyauthorstyles="false">
	<implementation>
		<constructor><![CDATA[
			(function(block){
				setTimeout(function(){
					block.style.mozBinding = '';
  					var t = document.createElement('DIV');
					while (block.firstChild)
				  		t.appendChild(block.firstChild);
					block.appendChild(t);
					block.appendChild(document.createElement('DIV'));
					block.className = block.className + ' moz-ellipsis';
				}, 0);
			})(this);
		]]></constructor>
	</implementation>
</binding>

</bindings>

Что тут происходит? Мы воссоздаем нашу начальную HTML структуру. То есть те сложности с блоками делаются автоматически, и только в Firefox. JavaScript код написан в лучших традициях :)
К сожалению, ситуацию, когда текст обрезается посередине буквы, мы избежать не можем (правда, возможно, временно, так как мое такое решение пока еще очень сырое, и в будущем может получится). Но можем немного сгладить этот эффект. Для этого нам понадобится изображение (белый фон с прозрачным градиентом), и немного изменений в стиль:
    .moz-ellipsis > DIV:first-child
    {
      float: left;
      margin-right: -26px;
    }
    .moz-ellipsis > DIV + DIV
    {
      float: right;
      margin-top: -1.2em;
      background: url(ellipsis.png) repeat-y;
      padding-left: 26px;
    }

Смотрим и радуемся жизни.

На этом и поставим точку.

Заключение



Приведу небольшой пример, для сторонней верстки. Я взял оглавление одной из страниц Wikipedia (первое что подвернулось), и применил для него описанный выше метод.
Вообще же данное решение можно назвать универсальным лишь с натяжкой. Все зависит от вашей верстки и ее сложности. Возможно, понадобится напильник, а может и бубен. Хотя в большинстве случаев, я думаю, работать будет. И потом, у вас теперь есть отправная точка ;)
Надеюсь, вы почерпнули из статьи что-то интересное и полезное ;) Учитесь, экспериментируйте, делитесь.
Удачи!
Tags:
Hubs:
+53
Comments 48
Comments Comments 48

Articles