Проглючило: делаем глитч-эффект на 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>:

    
language: HTML
<!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>

Теперь нуж­но настро­ить внеш­ний вид стра­ни­цы и доба­вить сами эффек­ты.

Настраиваем внешний вид страницы

Что­бы эффект был похож на тот, что мы виде­ли на кар­тин­ке в самом нача­ле, сде­ла­ем фон чёр­ным, а текст — белым и выров­ня­ем по цен­тру:

    
language: SCSS
* {

   /*убираем отступы*/

   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:

    
language: SCSS
&: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 кадр, и каж­дый кадр отри­су­ем в пря­мо­уголь­ни­ке со слу­чай­ны­ми началь­ны­ми коор­ди­на­та­ми, что­бы уси­лить эффект:

    
language: SCSS
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 пик­се­лей нуж­но для того, что­бы эффект заце­пил всю над­пись. Если будет мень­ше, то послед­ние сим­во­лы оста­нут­ся без эффек­тов.

Готовый SCSS-код:

    
language: SCSS
* {

   /*убираем отступы*/

   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)

      }

   }

}


Ско­пи­ро­вать код
Код ско­пи­ро­ван
Сконвертированный CSS-код, который можно добавить на страницу

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

    
language: CSS
* {

  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. Как при­ме­нить этот эффект к кон­крет­ной бук­ве?