Иногда на модных сайтах и в рекламных роликах можно встретить интересный эффект. Текст как будто немного нестабилен: дрожит, странно моргает и как-то сдвигается и сразу возвращается на место. Как будто кто-то теребит кабель подключения монитора. Это называется глитч, от английского glitch — сбой или глюк.
Сегодня мы сделаем нечто подобное, используя препроцессор SASS — один из любимых инструментов фронтенд-разработчиков. Формат записи выберем SCSS — так процесс будет выглядеть проще и понятнее. Заодно и попрактикуемся в новом языке.
О препроцессорах мы писали буквально вчера. Сам глитч-эффект выглядит примерно так:
Как мы это сделаем
- Нам понадобится простая HTML-страница, на которой мы разместим текст.
- Этот текст мы поместим в специальный блок <div>, к которому будем применять глитч-эффекты. Чтобы CSS понял, с чем ему нужно работать, у блока должно быть имя.
- Настроим CSS-стили. Так браузер поймёт, как ему нужно отобразить этот текст, чтобы проявилась магия глитча.
- Работать с CSS будем с помощью препроцессора SASS — это почти тот же CSS, только с дополнительными возможностями.
👉 Важное замечание: SCSS-код нельзя просто вставить в HTML-страницу — браузер не поймёт, что вы имели в виду, и будет обрабатывать это как обычный CSS-код. Когда работают с препроцессорами, перед публикацией страницы код преобразуют в привычный CSS-формат, который и вставляют в раздел <style> или сохраняют как обычный .css-файл. Чтобы вы могли вставить готовый код, мы сами преобразуем его из SCSS в CSS и выложим в конце статьи.
Готовим HTML-код
Самый простой этап. Мы просто берём наш шаблон пустой страницы и вставляем в него заголовок <h1>:
<!DOCTYPE html>
<html>
<!-- служебная часть -->
<head>
<!-- заголовок страницы -->
<title>Заголовок</title>
<!-- настраиваем служебную информацию для браузеров -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
/* тут будут стили */
</style>
<!-- закрываем служебную часть страницы -->
</head>
<body>
<h1>
Журнал «КОД»
</h1>
</body>
<!-- конец всей страницы -->
</html>
Делаем специальную разметку
У нас пока на странице есть просто заголовок первого уровня. Но для глитч-эффекта нужно этот заголовок поместить в блок <div> и дать ему новый класс, он будет отвечать за эффект в целом. Сами визуальные фишки мы пропишем во вложенном элементе <h1>, которому тоже присвоим новый класс.
Сделаем это так:
<div class="box">
<h1 class="glitch">
Журнал «КОД»
</h1>
</div>
Теперь нужно настроить внешний вид страницы и добавить сами эффекты.
Настраиваем внешний вид страницы
Чтобы эффект был похож на тот, что мы видели на картинке в самом начале, сделаем фон чёрным, а текст — белым и выровняем по центру:
* {
/*убираем отступы*/
margin: 0;
padding: 0;
}
body,
html {
/*пусть страница занимает всё место в окне браузера*/
width: 100%;
height: 100%;
}
body {
/*делаем чёрный фон*/
background-color: #000;
}
/*стиль нашего блока*/
.box {
/*говорим браузеру, что все элементы этого класса должны быть по центру окна*/
display: flex;
justify-content: center;
align-items: center;
/*блок будет занимать всё доступное место по высоте*/
height: 100%;
.glitch {
color: #fff;
font-family: "Arial", sans-serif;
font-weight: 600;
font-size: 100px;
position: relative;
padding: 30px;
}
}
Что тут происходит:
- Так как это препроцессор, то мы используем упрощённый способ записи CSS-кода.
- Звёздочка в самом начале говорит о том, что эти стили применяются к каждому элементу, чтобы ни у кого не было отступов.
- После этого мы задали стили для всего документа и содержимого страницы — сказали, что проект должен занимать всё свободное пространство в окне браузера.
- Отдельно указали, что фон должен быть чёрным.
- Рассказали браузеру, как должен выглядеть блок с классом
box
. - Внутри класса
box
мы объявили классglitch
— он у нас отвечает за стиль самого заголовка. В нём мы прописали белый цвет для текста и сделали его жирнее, но это не всё, что там должно быть. Остальное добавим на следующем шаге.
Как устроен глитч-эффект
Чтобы добиться эффекта искажения, разработчики используют псевдоклассы :before
и :after
— они показывают тот же самый текст, но позади или спереди основного, который у нас прописан в <h1>. Псевдоэлемент означает, что его как бы нет на самом деле, но браузер может с ним работать так, как будто он есть.
Эффект получается в том случае, когда координаты основного и псевдотекста не совпадают и он вроде как проглядывает через основной текст. Чтобы так сделать, мы будем брать координаты основного текста, фиксировать их, а потом смещать на какое-то расстояние. Чем больше расстояние — тем сильнее эффект.
Запишем то, что мы только что сказали, на SCSS, и сделаем это внутри класса glitch
:
&:before,
&:after {
content: "Журнал «КОД»";
color: #fff;
position: absolute;
top: 0;
overflow: hidden;
padding: 30px;
}
&:before {
left: 3px;
text-shadow: -3px 0 red;
animation: glitch-before 2s linear 0s infinite alternate;
}
&:after {
left: -3px;
text-shadow: -3px 0 blue;
animation: glitch-after 2s linear 0s infinite alternate;
}
Что тут происходит:
- Задали общие свойства для псевдоэлементов — текст, который совпадает с текстом нашего заголовка, его цвет и отступ в 30 пикселей, как в родительском классе выше.
- Там же сказали, что местоположение этих псевдоэлементов будет абсолютным, то есть можно будет указывать в пикселях, на сколько его можно сместить в любую сторону.
- Hidden означает, что тексту нельзя будет вылезать за пределы нашего заголовка, чтобы эффект получился какой нужно.
- Дальше мы описали сами псевдоэлементы &:before и &:after уже по отдельности.
- Первому мы сказали, что он сдвинется влево на 3 пикселя, отбросит тень на три пикселя вправо и настроили анимацию: он сделает это на 2 секунды, а потом снова станет обычным элементом. У нас стоит бесконечное повторение, но визуально мы пока этого не заметим.
- Второму мы сказали то же самое, но про синий цвет и про смещение влево.
Финал: оживляем текст
Чтобы добавить огня, используем знакомый нам по JavaScript цикл @for. Его нет в стандартном CSS, но мы же используем препроцессоры, поэтому не будем себя ограничивать.
Нам понадобится команда @keyframes — она отвечает в CSS за раскадровку анимации. Проще говоря, если мы зададим кадры с параметрами 0%, 50% и 100%, то у нас в начале, середине и конце анимации могут быть разные картинки.
Задача такая: нужно сделать анимацию из 20 кадров для каждого элемента, чтобы любой кадр немного отличался от остальных. Это даст тот самый рваный и непредсказуемый эффект, который нам нужен. Для этого мы в цикле прогоним переменную i
от 0 до 20, чтобы получился 21 кадр, и каждый кадр отрисуем в прямоугольнике со случайными начальными координатами, чтобы усилить эффект:
keyframes glitch-before {
$steps: 20;
@for $i from 0 through $steps {
#{percentage($i*(1/$steps))} {
clip: rect(random(150) + px, 780px, random(150) + px, 30px);
}
}
}
@keyframes glitch-after {
$steps: 20;
@for $i from 0 through $steps {
#{percentage($i*(1/$steps))} {
clip: rect(random(150) + px, 780px, random(150) + px, 30px);
}
}
}
Что тут происходит:
- Мы задали отдельную раскадровку для каждого из двух псевдоэлементов.
- Анимация каждого такого элемента будет состоять из 21 кадра (20 + нулевой начальный).
- В цикле @for мы задаём раскадровку с шагом в 5% (1/20).
- На каждом проходе цикла рисуем виртуальный прямоугольник со случайными координатами по высоте. Этот прямоугольник как бы «обрезает» проглючившую надпись (от английского clip), то есть глючная версия надписи прорывается кусками.
- Значение в 780 пикселей нужно для того, чтобы эффект зацепил всю надпись. Если будет меньше, то последние символы останутся без эффектов.
* {
/*убираем отступы*/
margin: 0;
padding: 0;
}
body,
html {
/*пусть страница занимает всё место в окне браузера*/
width: 100%;
height: 100%;
}
body {
/*делаем чёрный фон*/
background-color: #000;
}
/*стиль нашего блока*/
.box {
/*говорим браузеру, что все элементы этого класса должны быть по центру окна*/
display: flex;
justify-content: center;
align-items: center;
/*блок будет занимать всё доступное место по высоте*/
height: 100%;
.glitch {
color: #fff;
font-family: "Poppins", sans-serif;
font-weight: 600;
font-size: 100px;
position: relative;
padding: 30px;
&:before,
&:after {
content: "Glitch";
color: #fff;
position: absolute;
top: 0;
overflow: hidden;
padding: 30px;
}
&:before {
left: 3px;
text-shadow: -3px 0 red;
animation: glitch-before 2s linear 0s infinite alternate;
}
&:after {
left: -3px;
text-shadow: -3px 0 blue;
animation: glitch-after 2s linear 0s infinite alternate;
}
}
}
@keyframes glitch-before {
$steps: 20;
@for $i from 0 through $steps {
#{percentage($i*(1/$steps))} {
clip: rect(random(150) + px, 350px, random(150) + px, 30px);
}
}
}
@keyframes glitch-after {
$steps: 20;
@for $i from 0 through $steps {
#{percentage($i*(1/$steps))} {
clip: rect(random(150) + px, 350px, random(150) + px, 30px);
}
}
}
* {
margin: 0;
padding: 0;
}
body,
html {
width: 100%;
height: 100%;
}
body {
background-color: #000;
}
.box {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.box .glitch {
color: #fff;
font-family: "Poppins", sans-serif;
font-weight: 600;
font-size: 100px;
position: relative;
padding: 30px;
}
.box .glitch:before,
.box .glitch:after {
content: "Glitch";
color: #fff;
position: absolute;
top: 0;
overflow: hidden;
padding: 30px;
}
.box .glitch:before {
left: 3px;
text-shadow: -3px 0 red;
animation: glitch-before 2s linear 0s infinite alternate;
}
.box .glitch:after {
left: -3px;
text-shadow: -3px 0 blue;
animation: glitch-after 2s linear 0s infinite alternate;
}
@keyframes glitch-before {
0% {
clip: rect(45px, 350px, 56px, 30px);
}
5% {
clip: rect(102px, 350px, 52px, 30px);
}
10% {
clip: rect(30px, 350px, 93px, 30px);
}
15% {
clip: rect(122px, 350px, 91px, 30px);
}
20% {
clip: rect(52px, 350px, 69px, 30px);
}
25% {
clip: rect(89px, 350px, 141px, 30px);
}
30% {
clip: rect(80px, 350px, 24px, 30px);
}
35% {
clip: rect(21px, 350px, 3px, 30px);
}
40% {
clip: rect(87px, 350px, 13px, 30px);
}
45% {
clip: rect(5px, 350px, 106px, 30px);
}
50% {
clip: rect(138px, 350px, 115px, 30px);
}
55% {
clip: rect(91px, 350px, 105px, 30px);
}
60% {
clip: rect(92px, 350px, 25px, 30px);
}
65% {
clip: rect(69px, 350px, 108px, 30px);
}
70% {
clip: rect(67px, 350px, 20px, 30px);
}
75% {
clip: rect(42px, 350px, 46px, 30px);
}
80% {
clip: rect(94px, 350px, 48px, 30px);
}
85% {
clip: rect(11px, 350px, 101px, 30px);
}
90% {
clip: rect(135px, 350px, 104px, 30px);
}
95% {
clip: rect(128px, 350px, 69px, 30px);
}
100% {
clip: rect(26px, 350px, 116px, 30px);
}
}
@keyframes glitch-after {
0% {
clip: rect(137px, 350px, 103px, 30px);
}
5% {
clip: rect(29px, 350px, 77px, 30px);
}
10% {
clip: rect(148px, 350px, 150px, 30px);
}
15% {
clip: rect(60px, 350px, 65px, 30px);
}
20% {
clip: rect(99px, 350px, 54px, 30px);
}
25% {
clip: rect(104px, 350px, 11px, 30px);
}
30% {
clip: rect(45px, 350px, 82px, 30px);
}
35% {
clip: rect(34px, 350px, 10px, 30px);
}
40% {
clip: rect(1px, 350px, 11px, 30px);
}
45% {
clip: rect(119px, 350px, 93px, 30px);
}
50% {
clip: rect(19px, 350px, 20px, 30px);
}
55% {
clip: rect(26px, 350px, 84px, 30px);
}
60% {
clip: rect(63px, 350px, 44px, 30px);
}
65% {
clip: rect(21px, 350px, 143px, 30px);
}
70% {
clip: rect(45px, 350px, 132px, 30px);
}
75% {
clip: rect(65px, 350px, 105px, 30px);
}
80% {
clip: rect(75px, 350px, 124px, 30px);
}
85% {
clip: rect(101px, 350px, 16px, 30px);
}
90% {
clip: rect(20px, 350px, 116px, 30px);
}
95% {
clip: rect(133px, 350px, 102px, 30px);
}
100% {
clip: rect(9px, 350px, 147px, 30px);
}
}
Что дальше
Чтобы было интереснее, мы составили для вас список вопросов на подумать. Каждый ответ делает вас круче как специалиста, поэтому постарайтесь ответить на все:
- Почему пробел убирает один из эффектов со второго слова?
- Что будет, если у виртуального прямоугольника поменять координаты?
- А если убрать случайные значения из цикла?
- Что будет, если поменять местами порядок псевдоэлементов?
- Как применить этот эффект к конкретной букве?