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

    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) 100%); /* 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) 100%);
      -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="1" stop-color="#00babb" stop-opacity="0" />
    </linearGradient>
    

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

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

    <svg xmlns="http://www.w3.org/2000/svg">
      <linearGradient id="textEclipseGradientId">
        <stop offset="0.5" stop-color="#00babb" />
        <stop offset="1" 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.3em от размера шрифта (Рис. 9).

    Разметка:

    <svg xmlns="http://www.w3.org/2000/svg">
      <linearGradient id="textEclipseGradientId">
        <stop offset="0.5" stop-color="#00babb" />
        <stop offset="1" stop-color="#00babb" stop-opacity="0" />
      </linearGradient>
      <text y="50%" dy="0.3em" 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="1" stop-color="#00babb" stop-opacity="0" />
        </linearGradient>
        <text y="50%" dy="0.3em" fill="url(#textEclipseGradientId)">Johnny Sm</text>
      </svg>
      <span>Johnny Sm</span>
    </span>
    

    Стилизация:

    .text-eclipse {
      position: relative;
      display: inline-block;
    }
    
    .text-eclipse svg {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
    
    .text-eclipse span {
      color: transparent;
    }
    

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

    Стилизация:

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

    В принципе все. Давайте отшлифуем некоторые моменты и добавим 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.1" stop-opacity="0" />
        </linearGradient>
        <text y="50%" dy="0.3em" 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;
      display: inline-block;
    }
    
    .text-eclipse svg {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 0;
      width: 100%;
      height: 100%;
    }
    
    .text-eclipse stop {
      stop-color: currentColor;
    }
    
    .text-eclipse span {
      position: relative;
      z-index: 5; /* 1  */
      color: transparent;
    }
    

    1. делаем HTML-элемент главным в потоке, так как SVG выступает лишь в качестве «маски».


    Демо

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

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

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

    Итого


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

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

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

    Спасибо.

    UPD:
    • Добавил примечание на счёт поддержки Edge свойства background-clip: text. Спасибо monochromer.
    • Верно было замечено про момент с выделением текста, который я пропустил. Спасибо questor
    • Ответил в ветке по поводу замечания «Как быть с многострочным текстом?». Спасибо kazmiruk. Создал демо.
    • Правки по Решению 3: исправил недочёты в SVG и CSS коде; добавил ссылку на codepen с демо; добавил ещё один недостаток третьего решения про смену цвета.

    Примечание


    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
    Space307
    0.00
    Company
    Share post

    Comments 17

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

      Почему бы просто не сделать что-то подобное в таком случае:
      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
        Спасибо за комментарий.

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

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



            Конечно, этот подход будет работать и в IE, и в Edge.
            0
            Ну первая проблема вполне решается:

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

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

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

                0
                Спасибо за замечание. Да, в версиях Edge >= 12 свойство поддерживается. Обновил статью.
            0
            А вот идею в каких-нибудь лентах при наборе вниз страницу делать всё менее читаемой по мере набора текста — было бы интересно погонять на реальном применении. Не жёсткое ограничение на число символов, а вот так — постепенно, только действительно постепенно, за десяток строчек пройти градиент меж «плохо читается» до «совсем не читается» (но ещё набирается)… Копипаста, конечно спасёт бота, но автор всё равно будет знать, что легко читаться будет только первая треть…
              0
              А зачем? Чтобы потакать нежеланию неспособных читать «многабукаф»?
                0
                Хм, не подумал. Я имел в виду — как-то мотивировать льющих воду повышать качество своей речи.
                  +1
                  как-то мотивировать льющих воду повышать качество своей речи

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

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

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

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

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

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

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

                Only users with full accounts can post comments. Log in, please.