Проглючило: делаем глитч-эффект на SASS
medium

Проглючило: делаем глитч-эффект на SASS

На это можно смотреть бесконечно.

Иногда на модных сайтах и в рекламных роликах можно встретить интересный эффект. Текст как будто немного нестабилен: дрожит, странно моргает и как-то сдвигается и сразу возвращается на место. Как будто кто-то теребит кабель подключения монитора. Это называется глитч, от английского glitch — сбой или глюк.

Сегодня мы сделаем нечто подобное, используя препроцессор SASS — один из любимых инструментов фронтенд-разработчиков. Формат записи выберем SCSS — так процесс будет выглядеть проще и понятнее. Заодно и попрактикуемся в новом языке.

О препроцессорах мы писали буквально вчера. Сам глитч-эффект выглядит примерно так:

Как мы это сделаем

  1. Нам понадобится простая HTML-страница, на которой мы разместим текст.
  2. Этот текст мы поместим в специальный блок <div>, к которому будем применять глитч-эффекты. Чтобы CSS понял, с чем ему нужно работать, у блока должно быть имя.
  3. Настроим CSS-стили. Так браузер поймёт, как ему нужно отобразить этот текст, чтобы проявилась магия глитча.
  4. Работать с 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;
  }
}

Что тут происходит:

  1. Так как это препроцессор, то мы используем упрощённый способ записи CSS-кода.
  2. Звёздочка в самом начале говорит о том, что эти стили применяются к каждому элементу, чтобы ни у кого не было отступов.
  3. После этого мы задали стили для всего документа и содержимого страницы — сказали, что проект должен занимать всё свободное пространство в окне браузера.
  4. Отдельно указали, что фон должен быть чёрным.
  5. Рассказали браузеру, как должен выглядеть блок с классом box.
  6. Внутри класса 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;
}

Что тут происходит:

  1. Задали общие свойства для псевдоэлементов — текст, который совпадает с текстом нашего заголовка, его цвет и отступ в 30 пикселей, как в родительском классе выше.
  2. Там же сказали, что местоположение этих псевдоэлементов будет абсолютным, то есть можно будет указывать в пикселях, на сколько его можно сместить в любую сторону.
  3. Hidden означает, что тексту нельзя будет вылезать за пределы нашего заголовка, чтобы эффект получился какой нужно.
  4. Дальше мы описали сами псевдоэлементы &:before и &:after уже по отдельности.
  5. Первому мы сказали, что он сдвинется влево на 3 пикселя, отбросит тень на три пикселя вправо и настроили анимацию: он сделает это на 2 секунды, а потом снова станет обычным элементом. У нас стоит бесконечное повторение, но визуально мы пока этого не заметим.
  6. Второму мы сказали то же самое, но про синий цвет и про смещение влево.

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

Финал: оживляем текст

Чтобы добавить огня, используем знакомый нам по 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);
    }
  }
}

Что тут происходит:

  1. Мы задали отдельную раскадровку для каждого из двух псевдоэлементов.
  2. Анимация каждого такого элемента будет состоять из 21 кадра (20 + нулевой начальный).
  3. В цикле @for мы задаём раскадровку с шагом в 5% (1/20).
  4. На каждом проходе цикла рисуем виртуальный прямоугольник со случайными координатами по высоте. Этот прямоугольник как бы «обрезает» проглючившую надпись (от английского clip), то есть глючная версия надписи прорывается кусками.
  5. Значение в 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);
  }
}

Что дальше

Чтобы было интереснее, мы составили для вас список вопросов на подумать. Каждый ответ делает вас круче как специалиста, поэтому постарайтесь ответить на все:

  1. Почему пробел убирает один из эффектов со второго слова?
  2. Что будет, если у виртуального прямоугольника поменять координаты?
  3. А если убрать случайные значения из цикла?
  4. Что будет, если поменять местами порядок псевдоэлементов?
  5. Как применить этот эффект к конкретной букве?

Обложка:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Вам может быть интересно
medium
[anycomment]
Exit mobile version