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)


Рис. 2

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


Рис. 3

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


Рис. 4

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

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

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

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


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


Рис. 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;
}


Рис. 6

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

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

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


Рис. 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):


Рис. 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>


Рис. 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 подстраивается.

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

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

Спасибо.

UPD:


Примечание


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

Комментарии (17)


  1. EvilGenius18
    02.04.2018 18:24

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

    Почему бы просто не сделать что-то подобное в таком случае:
    codepen.io/anon/pen/pLKEVd

    <div>
      <h2>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit
      </h2>
    </div>
    


    h2 {
      background-image: linear-gradient(90deg, #000 0%, #000 70%, rgba(0,0,0,0));
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      display: inline-block;
    }


    1. inomdzhon92 Автор
      02.04.2018 19:13
      +1

      Спасибо за комментарий.

      В итогах решения под номером 1.1 я перечислил проблемы из-за которых отказался от данного метода.

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


      1. EvilGenius18
        02.04.2018 19:23

        Понятно, спасибо. Не обратил внимание.
        Хотя приведенный codepen все же работает и в Edge и в IE для этого конкретного примера.


        1. inomdzhon92 Автор
          03.04.2018 10:54

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



          Конечно, этот подход будет работать и в IE, и в Edge.


      1. Myateznik
        03.04.2018 10:37

        Ну первая проблема вполне решается:

        h2 {
          background-image: linear-gradient(90deg, currentColor 0%, currentColor 70%, rgba(0,0,0,0));
          -webkit-background-clip: text;
          -webkit-text-fill-color: transparent;
          display: inline-block;
        }

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


      1. monochromer
        03.04.2018 11:31

        Как ни странно, свойство -webkit-background-clip работает в последних Edge и Firefox


        1. inomdzhon92 Автор
          03.04.2018 11:32

          Спасибо за замечание. Да, в версиях Edge >= 12 свойство поддерживается. Обновил статью.


  1. impetus
    02.04.2018 19:17

    А вот идею в каких-нибудь лентах при наборе вниз страницу делать всё менее читаемой по мере набора текста — было бы интересно погонять на реальном применении. Не жёсткое ограничение на число символов, а вот так — постепенно, только действительно постепенно, за десяток строчек пройти градиент меж «плохо читается» до «совсем не читается» (но ещё набирается)… Копипаста, конечно спасёт бота, но автор всё равно будет знать, что легко читаться будет только первая треть…


    1. DoctorMoriarty
      02.04.2018 19:42

      А зачем? Чтобы потакать нежеланию неспособных читать «многабукаф»?


      1. impetus
        02.04.2018 20:22

        Хм, не подумал. Я имел в виду — как-то мотивировать льющих воду повышать качество своей речи.


        1. DoctorMoriarty
          02.04.2018 22:05

          как-то мотивировать льющих воду повышать качество своей речи

          А тут как-то навскидку ничего в голову не приходит, кроме всяких механизмов наподобие рейтинга публикации, который оказывает влияние на частоту дальнейших публикаций автором (достаточно грубая модель, разумеется).
          С одной стороны да — требование лаконичности отсекает бессмысленный поток спича, с другой же — если, где-то стоит ограничение на объем текста, иногда нереально выбешивает необходимость в процессе публикации удалять важные для смысла куски: дьявол-то бывает в деталях, а детали приходится сокращать.


          1. impetus
            03.04.2018 12:42

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


            1. DoctorMoriarty
              04.04.2018 14:34

              Охх… Но кто будет определять условно оптимальный размер публикации? Тем более, вангую — для разных тематик этот размер будет меняться в широком диапазоне. И как только встанет задача поиска оптимума (вангую еще раз) спекуляций в духе «британские ученые установили, что размер должен быть _вставить желаемое число_» будет over9000.


  1. questor
    03.04.2018 08:49
    +1

    А как текст из картинки скопипастить? Или вы не подумали о том, что кому-то понадобится скопировать текст из браузера.


    1. inomdzhon92 Автор
      03.04.2018 11:19

      Спасибо за комментарий.

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

      Это можно решить:

      • либо создав ещё один скрытый DOM элемент с полным текстом (он будет с абсолютным позиционированием) и установив user-select:none DOM элементу с обрезанным текстом
      • либо через JS прослушивать события mouseup/mousedown и отдавать значение из атрибута title


  1. kazmiruk
    03.04.2018 09:41

    Вариант когда у нас одна строка довольно примитивный. А вот когда у нас 3 строки и конец 3ей нужно уводить в градиент?


    1. inomdzhon92 Автор
      03.04.2018 12:21

      Спасибо за комментарий.

      Как частый случай примером был взят однострочный текст.

      Если говорить про сокращение многострочного контента, то нам понадобится на JS высчитать высоту блока, удалить лишние слова и обернуть последнею строку (или слово) в отдельный строчный DOM элемент, к которому, одним из методов приведённых в статье, будем применять эффект ухода в прозрачность. Вообще весь начальный алгоритм аналогичен тому, как мы бы обрезали многоточием.