Пересечение текста линией в CSS

Пересечение текста линией в CSS

От автора: в начале этого года я наткнулась на это демо от Флорина Попа, в нем линия проходит либо поверх, либо позади букв заголовка. Я подумала, что это крутая идея, но в реализации было несколько мелочей, которые я могла бы упростить и улучшить одновременно.

Пересечение текста линией в CSS

Во-первых, в оригинальной демо-версии текст заголовка дублируется, чего, как я знала, можно было легко избежать. Также длина линии, проходящей через текст, является магическим числом, что не является очень гибким подходом. И, наконец, можем ли мы избавиться от JavaScript?

Итак, давайте посмотрим, что у меня в конечном итоге получилось.

HTML структура

Флорин помещает текст в элемент заголовка, а затем дублирует этот заголовок, используя Splitting.js, чтобы заменить текстовое содержимое дублированного заголовка спанами, каждый из которых содержит одну букву исходного текста.

Решение сделать это с дублированием текста, используя библиотеку, чтобы разделить текст на символы, а затем поместить каждый из них в span, похожее на ненужное, мы делаем все это с помощью препроцессора HTML.

- let text = 'We Love to Play';
- let arr = text.split(''); h1(role='image' aria-label=text) - arr.forEach(letter => { span.letter #{letter} - });

Поскольку разделение текста на несколько элементов не подходит для программ чтения с экрана, мы задали для всего role со значением image и указали aria-label. Это генерирует следующий HTML:

<h1 role="image" aria-label="We Love to Play"> <span class="letter">W</span> <span class="letter">e</span> <span class="letter"> </span> <span class="letter">L</span> <span class="letter">o</span> <span class="letter">v</span> <span class="letter">e</span> <span class="letter"> </span> <span class="letter">t</span> <span class="letter">o</span> <span class="letter"> </span> <span class="letter">P</span> <span class="letter">l</span> <span class="letter">a</span> <span class="letter">y</span>
</h1>

Основные стили

Мы помещаем заголовок в центр его родителя (body в данном случае), используя grid-макет:

body { display: grid; place-content: center;
}

Пересечение текста линией в CSS

Заголовок не растягивается на всю ширину родителя, но вместо этого помещается в середине. Мы также можем добавить некоторые штрихи, например, красивые font или background для контейнера. Далее мы создаем линию с абсолютно позиционированным псевдо-элементом ::after, его толщина (height) $h:

$h: .125em;
$r: .5*$h; h1 { position: relative; &::after { position: absolute; top: calc(50% - #{$r}); right: 0; height: $h; border-radius: 0 $r $r 0; background: crimson; }
}

Приведенный выше код задает позиционировании и толщину псевдо-элемента, но что насчет его ширины? Как заставить его растягиваться от левого края области просмотра до правого края текста заголовка?

Длина линии

Итак, поскольку у нас есть grid макет, в котором заголовок выровнен по центру по горизонтали, это означает, что вертикальная средняя линия, делящая окно просмотра посередине, совпадает со средней линией для заголовка, разделяя их на две равные по ширине половины:

Пересечение текста линией в CSS

Следовательно, расстояние между левым краем области просмотра и правым краем заголовка составляет половину ширины области просмотра (50vw) плюс половина ширины заголовка, что может быть выражено как процентное значение при вычислении ширины псевдо-элементов.

Таким образом, width для нашего псевдо-элемента ::after будет:

width: calc(50vw + 50%);

Как сделать, чтобы линия размещалась перед и позади букв?

Пока что мы получили просто красную линию, пересекающую текст:

Мы хотим, чтобы некоторые буквы отображались поверх линии. Чтобы получить этот эффект, мы случайным образом задаем им (или не задаем) класс .over. Это означает, что нужно немного изменить код Pug:

- let text = 'We Love to Play';
- let arr = text.split(''); h1(role='image' aria-label=text) - arr.forEach(letter => { span.letter(class=Math.random() > .5 ? 'over' : null) #{letter} - });

Затем мы сопоставляем буквы с классом .over и задаем им положительный z-index.

.over { position: relative; z-index: 1;
}

Моя первоначальная идея заключалась в использовании translatez(1px) вместо z-index: 1, но потом мне показалось, что использование z-index имеет лучшую поддержку браузеров и требует меньше усилий.

Линия проходит поверх одних букв, но под другими:

Анимация

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

@keyframes slide { 0% { transform: translate(-100%); } }

Я предпочла, чтобы прежде чем запускается анимация линии, прошло немного времени. Это означало добавление 1s задержки, что, в свою очередь, означает добавление ключевого слова backwards для animation-fill-mode, чтобы линия до начала анимации находилась в позиции 0%:

animation: slide 2s ease-out 1s backwards;

Немного 3D

Это дало мне еще одну идею, заключающуюся в том, чтобы линия пронизывала буквы, входила в начале поверх буквы, а затем уходила под нее (или наоборот). Это требует реального 3D и нескольких небольших настроек.

Во-первых, мы устанавливаем для transform-style заголовока значение preserve-3d, так как хотим, чтобы все его потомки (и псевдо-элементы) были частью одного и того же 3D-контекста, что позволит их размещать и пересекать так, как они расположены в 3D.

Затем нам нужно повернуть каждую букву вокруг своей оси y, причем направление вращения зависит от наличия случайно назначенного класса (имя которого мы меняем на .rev от «reverse», так как «over» не совсем соответствует тому, что мы здесь делаем).

Однако, прежде чем мы сделаем это, мы должны помнить, что элементы span в данный момент все еще являются встроенными, и указание transform для встроенного элемента не дает абсолютно никакого эффекта.

Чтобы обойти эту проблему, мы задали для заголовка display: flex. Однако это создает новую проблему – ширина элементов span, содержащих только пробел (» «), сбрасывается на ноль.

Пересечение текста линией в CSS

Простое решение этой проблемы — установить white-space: pre для наших спанов с классом .letter. Как только мы это сделаем, мы сможем повернуть спаны на угол $a… в одном или другом направлении!

$a: 2deg; .letter { white-space: pre; transform: rotatey($a);
} .rev { transform: rotatey(-$a); }

Поскольку вращение вокруг оси Y сжимает буквы по горизонтали, мы можем масштабировать их вдоль оси X с помощью коэффициента ($f), обратного косинусу $a.

$a: 2deg;
$f: 1/cos($a) .letter { white-space: pre; transform: rotatey($a) scalex($f)
} .rev { transform: rotatey(-$a) scalex($f) }

Вот и все! Теперь у нас есть 3D-результат, к которому мы стремились! Однако обратите внимание, что используемый здесь шрифт был выбран так, чтобы результат выглядел хорошо, и другой шрифт может не подходить так же хорошо.

Автор: Ana Tudor

Источник: https://css-tricks.com

Редакция: Команда webformyself.