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

в 12:58, , рубрики: css, svg, svg mask, web-разработка, Блог компании Space307, Работа с векторной графикой, Разработка веб-сайтов

image

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

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

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

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

Example 1
Рис. 1

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

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

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

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

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

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

Разметка:

<span class="text-eclipse" aria-label="Johnny Smith" title="Johnny Smith">Johnny Sm</span>

Стилизация:

.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)

Ограничение длины текста через градиент - 3
Рис. 2

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

Ограничение длины текста через градиент - 4
Рис. 3

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

Ограничение длины текста через градиент - 5
Рис. 4

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

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

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

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

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

Ограничение длины текста через градиент - 6
Рис. 5

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

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

Разметка:

<span class="text-eclipse" aria-label="Johnny Smith" title="Johnny Smith">Johnny Sm</span>

Стилизация:

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

Ограничение длины текста через градиент - 7
Рис. 6

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

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

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

Ограничение длины текста через градиент - 8
Рис. 7

Решение 2. SVG

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

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

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

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

<linearGradient>
  <stop offset="0.5" stop-color="#00babb" />
  <stop offset="0.95" stop-color="#00babb" stop-opacity="0" />
</linearGradient>

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

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

<svg xmlns="http://www.w3.org/2000/svg">
  <linearGradient id="textEclipseGradientId">
    <stop offset="0.5" stop-color="#00babb" />
    <stop offset="0.95" stop-color="#00babb" stop-opacity="0" />
  </linearGradient>
  <text fill="url(#textEclipseGradientId)">Johnny Sm</text>
</svg>

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

Ограничение длины текста через градиент - 9
Рис. 8

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

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

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

Разметка:

<svg xmlns="http://www.w3.org/2000/svg">
  <linearGradient id="textEclipseGradientId">
    <stop offset="0.5" stop-color="#00babb" />
    <stop offset="0.95" stop-color="#00babb" stop-opacity="0" />
  </linearGradient>
  <text y="50%" dy="0.36em" fill="url(#textEclipseGradientId)">Johnny Sm</text>
</svg>

Ограничение длины текста через градиент - 10
Рис. 9

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

Разметка:

<span class="text-eclipse">
  <svg xmlns="http://www.w3.org/2000/svg">
    <linearGradient id="textEclipseGradientId">
      <stop offset="0.5" stop-color="#00babb" />
      <stop offset="0.95" stop-color="#00babb" stop-opacity="0" />
    </linearGradient>
    <text y="50%" dy="0.34em" fill="url(#textEclipseGradientId)">Johnny Sm</text>
  </svg>
  <span>Johnny Sm</span>
</span>

Стилизация:

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

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

Стилизация:

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

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

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

Разметка:

<span class="text-eclipse">
  <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="0" height="0">
    <linearGradient id="textEclipseGradientId">
      <stop offset="0.5" />
      <stop offset="0.95" stop-opacity="0" />
    </linearGradient>
    <text y="50%" dy="0.34em" fill="url(#textEclipseGradientId)">Johnny Sm</text>
  </svg>
  <span aria-label="Johnny Smith" title="Johnny Smith">Johnny Sm</span>
</span>

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

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

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

Если говорить про кроссбраузерность, то должно работать везде, где поддерживается 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

Автор: Мирджамолов Иномджон

Источник


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


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