Рассмотрим создание эффекта ухода текста в прозрачность как альтернативу обрезания текста многоточием.
Наверняка вы замечали, а может, и вовсе использовали на практике такой прием, как обрезание длинных слов многоточием, дабы те вписывались в дизайн.
Частый кейс ограничения длины текста — это имя пользователя. При этом не всегда под корень вырезают буквы, выходящие за допустимые рамки. Пользователь может каким-либо образом увидеть имя целиком, например, на наведение мыши ему показывается всплывающая подсказка с полным именем.
Но многоточие — не единственный способ решения. К примеру, нам в команде понравился вариант с уходом длинных имен плавно в прозрачность (Рис. 1)[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 */
}
- ширину лучше зададим в относительных единицах, чтобы не делать лишних операций при изменении размеров шрифта;
- по стандарту
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;
}
- хак, который принуждает
<linearGradient>
инициализироваться заново, чтобы сработала смена цвета; - делаем HTML-элемент главным в потоке, так как SVG выступает лишь в качестве «маски».
И тут не без проблем:
- значение
dy
для вертикального выравнивания строго зависит от используемого семейства шрифта; - в зависимости от шрифта «хвост» первой буквы в слове может обрезаться (можно указать
overflow: visible
у<svg>
, но это не сработает в IE и Safari); - так как в SVG у масок, градиентов, паттернов и т.п. должен быть уникальный id для их применения, для
<linearGradient>
следует генерировать новый id во избежание конфликтов между несколькими такими элементами. Вынести в один градиент не получится, так мы наследуем цвет от конкретного<svg>
.
Если говорить про кроссбраузерность, то должно работать везде, где поддерживается SVG 1.0.
Итого
Из рассмотренных решений я, конечно же, отдаю свое предпочтение варианту с SVG, так как он намного гибче, чем остальные. Нас совсем не заботит ни цвет фона, ни градиент. Мы работаем с обычным текстом, а SVG подстраивается.
Вообще, задача действительно простая, нужна только внимательность и хорошая осведомленность, чтобы решить её быстро.
Если у вас есть замечания или предложения, то прошу поделиться ими в комментариях к статье.
Спасибо.
UPD:
- Добавил примечание на счёт поддержки Edge свойства
background-clip: text
. Спасибо monochromer. - Верно было замечено про момент с выделением текста, который я пропустил. Спасибо questor
- Ответил в ветке по поводу замечания «Как быть с многострочным текстом?». Спасибо kazmiruk.
Примечание
- В примере мы имеем ограничение в 9 символов. Задача, где срезать лишние символы — на сервере или на клиенте, сугубо индивидуальная.
- CR — Candidate Recommendation.
- CSS Image Values and Replaced Content Module Level 3; 4.4 Gradient Color-Stops
- Баг с transparent в Safari
background-clip: text
не работает в версиях Edge < 12
Комментарии (17)
impetus
02.04.2018 19:17А вот идею в каких-нибудь лентах при наборе вниз страницу делать всё менее читаемой по мере набора текста — было бы интересно погонять на реальном применении. Не жёсткое ограничение на число символов, а вот так — постепенно, только действительно постепенно, за десяток строчек пройти градиент меж «плохо читается» до «совсем не читается» (но ещё набирается)… Копипаста, конечно спасёт бота, но автор всё равно будет знать, что легко читаться будет только первая треть…
DoctorMoriarty
02.04.2018 19:42А зачем? Чтобы потакать нежеланию неспособных читать «многабукаф»?
impetus
02.04.2018 20:22Хм, не подумал. Я имел в виду — как-то мотивировать льющих воду повышать качество своей речи.
DoctorMoriarty
02.04.2018 22:05как-то мотивировать льющих воду повышать качество своей речи
А тут как-то навскидку ничего в голову не приходит, кроме всяких механизмов наподобие рейтинга публикации, который оказывает влияние на частоту дальнейших публикаций автором (достаточно грубая модель, разумеется).
С одной стороны да — требование лаконичности отсекает бессмысленный поток спича, с другой же — если, где-то стоит ограничение на объем текста, иногда нереально выбешивает необходимость в процессе публикации удалять важные для смысла куски: дьявол-то бывает в деталях, а детали приходится сокращать.impetus
03.04.2018 12:42именно — просто ограничение бесит, возможно (пробовать надо) — мягкое ограничение градиентом (размером шрифта, сложной прокруткой и т.п.) будет бесить меньше
DoctorMoriarty
04.04.2018 14:34Охх… Но кто будет определять условно оптимальный размер публикации? Тем более, вангую — для разных тематик этот размер будет меняться в широком диапазоне. И как только встанет задача поиска оптимума (вангую еще раз) спекуляций в духе «британские ученые установили, что размер должен быть _вставить желаемое число_» будет over9000.
questor
03.04.2018 08:49+1А как текст из картинки скопипастить? Или вы не подумали о том, что кому-то понадобится скопировать текст из браузера.
inomdzhon92 Автор
03.04.2018 11:19Спасибо за комментарий.
Не совсем понял про картинку, ведь пользователь видит обычный текст.
Но да, спасибо за замечание, с выделением текста действительно есть проблема.
Это можно решить:
- либо создав ещё один скрытый DOM элемент с полным текстом (он будет с абсолютным позиционированием) и установив
user-select:none
DOM элементу с обрезанным текстом - либо через JS прослушивать события mouseup/mousedown и отдавать значение из атрибута
title
- либо создав ещё один скрытый DOM элемент с полным текстом (он будет с абсолютным позиционированием) и установив
kazmiruk
03.04.2018 09:41Вариант когда у нас одна строка довольно примитивный. А вот когда у нас 3 строки и конец 3ей нужно уводить в градиент?
inomdzhon92 Автор
03.04.2018 12:21Спасибо за комментарий.
Как частый случай примером был взят однострочный текст.
Если говорить про сокращение многострочного контента, то нам понадобится на JS высчитать высоту блока, удалить лишние слова и обернуть последнею строку (или слово) в отдельный строчный DOM элемент, к которому, одним из методов приведённых в статье, будем применять эффект ухода в прозрачность. Вообще весь начальный алгоритм аналогичен тому, как мы бы обрезали многоточием.
EvilGenius18
Вы серьезно проделали столько работы только для того, чтобы иметь возможность плавно спрятать конец захардкоденного текста?
Почему бы просто не сделать что-то подобное в таком случае:
codepen.io/anon/pen/pLKEVd
inomdzhon92 Автор
Спасибо за комментарий.
В итогах решения под номером 1.1 я перечислил проблемы из-за которых отказался от данного метода.
EvilGenius18
Понятно, спасибо. Не обратил внимание.
Хотя приведенный codepen все же работает и в Edge и в IE для этого конкретного примера.
inomdzhon92 Автор
Так вы же используете метод из первого решения.
Конечно, этот подход будет работать и в IE, и в Edge.
Myateznik
Ну первая проблема вполне решается:
Правда ввиду невозможности наследования прозрачного цвета (тот же цвет, но с альфа каналом в нуле) и особенностей обработки градиентов в некоторых браузерах может получиться эффект перехода любого цвета в прозрачный чёрный.
monochromer
Как ни странно, свойство
-webkit-background-clip
работает в последних Edge и Firefoxinomdzhon92 Автор
Спасибо за замечание. Да, в версиях Edge >= 12 свойство поддерживается. Обновил статью.