Ограничение длины текста через градиент

image

Рассмотрим создание эффекта ухода текста в прозрачность как альтернативу обрезания текста многоточием.

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

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

Но многоточие — не единственный способ решения. К примеру, нам в команде понравился вариант с уходом длинных имен плавно в прозрачность (Рис. 1)[1].

Example 1
Рис. 1

Способы его реализации далее и рассмотрим. В качестве примера будем использовать содержимое рисунка выше (Рис. 1). Будем все описывать, используя HTML и CSS. Без React«а и webpack«а, простите.

Решение 1. CSS (функция «linear-gradient»)


Первое что может прийти в голову — использовать CSS функцию linear-gradient.

Описываем прямоугольник:

  • высота равна кеглю;
  • ширина равна N (N зависит от того, насколько мы хотим покрыть градиентом участок);
  • в зависимости от цвета фона задаем градиент (одна из точек полностью прозрачная, другая сплошная);
  • абсолютным позиционированием закрепляем к правому краю блока с текстом.


Используя этот алгоритм, воссоздадим наш пример. Напоминаю, у нас бирюзовый текст на белом фоне.

Разметка:

Johnny Sm


Стилизация:

.text-eclipse {
  position: relative;
}

.text-eclipse::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 45%; /* 1 */
  height: 100%;
  background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgb(255, 255, 255) 87%); /* 2 */
}


  1. ширину лучше зададим в относительных единицах, чтобы не делать лишних операций при изменении размеров шрифта;
  2. по стандарту transparent — это на самом деле сокращение от rgba(0, 0, 0, 0) [3]. В связи с этим в Safari наблюдается баг [4].


В итоге мы получаем наш результат (Рис. 1).

Но что будет, если мы перекрасим фон? (Рис. 2)

bslk99h_yfemagumxaebbvhhxbq.png
Рис. 2

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

_xglvcq1cx_idmf17pqgwdzfoia.png
Рис. 3

Это был сплошной фон. Опустим вероятность того, что он меняется динамически, но то, что фон может быть задан как градиент, мы не должны исключать (Рис. 4).

5vdcgq2az3wugei1m9etscrn3ma.png
Рис. 4

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

Итак, минусы данного способа:

  • цвет градиента завязан на цвете фона, и об нужно знать и помнить;
  • если для фона применяется градиент, то мы ничего не сможем сделать.


Решение 1.1. CSS (свойство «background-clip»)


В CSS есть свойство background-clip (на момент написания статьи входит в статус CR[2] в спецификации), который по стандарту принимает три значения border-box, padding-box и content-box. Если добавить к свойству префикс -webkit-, появится ещё одно — text (Рис. 5). Как раз оно нам и нужно.

rxboj6ufedj0atnpk1lnz2dvu04.png
Рис. 5

На рисунке (Рис. 5) наглядно видно, как работает каждое значение. Важно, что в примере с text также задается прозрачный цвет шрифта, иначе применение свойства потеряло бы свой смысл, т.к. мы вовсе не увидели бы, как обрезается фон.

Благодаря такому поведению фона при background-clip можно решить нашу задачу. Задаем через градиент цвет шрифта, используя background (Рис. 6).

Разметка:

Johnny Sm


Стилизация:

.text-eclipse {
  background: linear-gradient(to right, rgb(0, 186, 187) 50%, rgba(0, 186, 187, 0) 95%);
  -webkit-background-clip: text;
  color: transparent;
}


ahhjbfvdnjly9pjfgsurke6r9_8.png
Рис. 6

Теперь мы никак не зависим от цвета фона.

Но, естественно, не бывает все «идеально». Есть как минимум два минуса:

  • проблематично менять цвет текста из-за использования linear-gradient. Хотелось бы, чтобы он наследовался и не заботил нас;
  • если нужна поддержка IE и Edge, то значение text у background-clip не удовлетворяет этому условию, из-за чего будет просто закрашенная фигура (Рис. 7).


azwtbab1pb4rt7etf7hhyzfox1y.png
Рис. 7

Решение 2. SVG


До того, как желание наконец-таки написать супербиблиотеку на Javascript, которая будет решать эту тривиальную задачу, одержит верх, стоит вспомнить про всеми любимый и гибкий SVG. Его возможности не ограничены созданием лишь примитивных фигур и кривых. Конкретно нас интересует элемент .

Заранее отметим следующее:

  • элементы SVG никак не определяют его размеры, за это отвечают width, height и viewBox (если width и height не объявлены). То есть в нашем случае каким бы длинным ни был текст, он не повлияет на родителя;
  • для стилизации любых SVG-элементов есть два основных атрибута: fill (цвет заливки) и stroke (цвет обводки). Если проводить аналогию с CSS, то первый — это сразу и backgound, и color, а второй — border-color. Получается мы можем спокойно залить градиентом с помощью fill.


Опишем для начала градиент:


  
  


Это эквивалентно записи linear-gradient(to right, rgb(0, 186, 187) 50%, rgba(0, 186, 187, 0) 95%).

Применим градиент к тексту:


  
    
    
  
  Johnny Sm


В итоге получаем (Рис. 8):

oxgzyxyl4cnzqlzbqzbqdm22rle.png
Рис. 8

Хм, что-то пошло не так. Давайте разберёмся.

С размерами все ясно: мы их не задали, поэтому применились размеры по умолчанию (300×150). Тогда получается проблема с позиционированием текста.

Для вертикального позиционирования существует атрибут y. Зададим ему половину высоты SVG. Как и с методом вертикального позиционирования блоков через top: 50% нам нужно сделать что-то наподобие transform: translateY(-50%). Нам поможет атрибут dy у элемента . Будем задавать относительной единицей. Это примерно 0.34em от размера шрифта (Рис. 9).

Разметка:


  
    
    
  
  Johnny Sm


ejftn8fefkohn51t0oalwtveauy.png
Рис. 9

Теперь разберемся с размерами. Раз элементы не могут менять размеры SVG, то создадим HTML-элемент с тем же текстом, а SVG будем позиционировать абсолютно относительно него.

Разметка:


  
    
      
      
    
    Johnny Sm
  
  Johnny Sm


Стилизация:

.text-eclipse {
  position: relative;
  line-height: 1;
}

.text-eclipse svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.text-eclipse svg + span {
  color: transparent;
}


Сейчас цвет зашит в элементе , а нам бы хотелось, чтобы он наследовался от color. Для этого нам нужно задать color: inherit и stop-color: currentColor для  и stop-color: inherit для . Но тут не все так просто. stop-color: inherit ведет себя как background: inherit, поэтому нужно применить его и к элементу .

Стилизация:

.text-eclipse svg {
  color: inherit;
  stop-color: currentColor;
}

.text-eclipse linearGradient,
.text-eclipse stop {
  stop-color: inherit;
}


В принципе все. Давайте отшлифуем некоторые моменты и добавим ARIA-атрибуты.

Разметка:


  
  Johnny Sm


Note:

  • на всякий случай задаем width="0" и height="0", чтобы страница не прыгала до применения CSS;
  • aria-hidden="true" скрываем от экранных читалок;


Стилизация:

.text-eclipse {
  position: relative;
  line-height: 1;
}

.text-eclipse svg {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  color: inherit;
  stop-color: currentColor;
}

.text-eclipse:hover svg {
  color: inherit; /* 1 */
}

.text-eclipse linearGradient,
.text-eclipse stop {
  stop-color: inherit;
}

.text-eclipse svg + span {
  position: relative;
  z-index: 5; /* 2 */
  color: transparent;
}


  1. хак, который принуждает инициализироваться заново, чтобы сработала смена цвета;
  2. делаем HTML-элемент главным в потоке, так как SVG выступает лишь в качестве «маски».


И тут не без проблем:

  • значение dy для вертикального выравнивания строго зависит от используемого семейства шрифта;
  • в зависимости от шрифта «хвост» первой буквы в слове может обрезаться (можно указать overflow: visible у , но это не сработает в IE и Safari);
  • так как в SVG у масок, градиентов, паттернов и т.п. должен быть уникальный id для их применения, для  следует генерировать новый id во избежание конфликтов между несколькими такими элементами. Вынести в один градиент не получится, так мы наследуем цвет от конкретного .


Если говорить про кроссбраузерность, то должно работать везде, где поддерживается SVG 1.0.

Итого


Из рассмотренных решений я, конечно же, отдаю свое предпочтение варианту с SVG, так как он намного гибче, чем остальные. Нас совсем не заботит ни цвет фона, ни градиент. Мы работаем с обычным текстом, а SVG подстраивается.

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

Если у вас есть замечания или предложения, то прошу поделиться ими в комментариях к статье.

Спасибо.

Примечание


  1. В примере мы имеем ограничение в 9 символов. Задача, где срезать лишние символы — на сервере или на клиенте, сугубо индивидуальная.
  2. CR — Candidate Recommendation.
  3. CSS Image Values and Replaced Content Module Level 3; 4.4 Gradient Color-Stops
  4. Баг с transparent в Safari

© Habrahabr.ru