Kawałek Kodu

Podejrzewam, że większość z Was jest w wieku, kiedy już dawno wyszumieliście się i udawanie chuligana żującego gumę, strzelającego z procy i rzucającego jabłkami jest trochę nie na miejscu. To nie jest już czas na wyszumianie się, więc pozostaje nam powprowadzać lekki chaos (ekranowy) w zaciszu komputera.

Jak zdać się na los.

W przypadku wszelkich maszyn elektronicznych mamy do czynienia z pseudolosowością, co oznacza, że wylosowana liczba jest całkowicie przewidywalna - nie ma prawdziwej losowości. Jest to delikatnie pocieszające, bo oznacza, że dziś nie będziemy musieli dążyć do ideału i nasz efekt nie musi być superidealny. Będziemy próbować zasymulować losowe tło - takie jak czasem widziałeś w telewizorze, kiedy Twoja ulubiona stacja przestała nadawać. Nie w sensie losowania konkretnego obrazka z puli obrazków, ale po prostu upstrzenia przestrzeni losowymi pikselami. Nie będziemy też używać gotowych wycinków teł w formacie PNG, bo nie w tym rzecz. Ma być to rozwiązanie pureCSS, a chcielibyśmy uzyskać coś takiego:

CSS póki co jest jeszcze bardziej ograniczony w stosunku do maszyny, na której działa niż ona sama. Tu nie mamy funkcji, która wylosuje nam jakąś wartość. Możemy to symulować po prostu cyklicznymi działaniami, których kroki sami ustalimy na losowe wartości. Możemy np. zbudować animację z 10 kroków, gdzie do każdego z kroków wstawimy jakieś wartości, ale z każdym jej końcem cykl zaczyna się powtarzać, więc w większym przedziale czasu całość nie będzie sprawiać wrażenia losowości. Dobrym rozwiązaniem jest nałożenie na siebie kilku takich pseudolosowych efektów. Jeśli kolejne efekty nie będą harmonicznymi opartymi o małe wartości (czyli kolejna animacja nie będzie np. 2, 3, 4 lub 5 razy szybsza), to nie będzie widać efektu nakładania się co n-tych kroków animacji. Ale to jedna część całego efektu. Potrzebujemy jeszcze samej podstawy, czyli naszej statycznej grafiki, która już w pewien sposób powinna być losowa.

Pijany rolnik.

Musimy zasiać piksele, a że potrzebujemy siać jak popadnie, to musimy znaleźć jakąś metodę. Tu podobnie jak wyżej wspomniana animacja, jesteśmy ograniczeni. Ograniczeni powtarzalnością gradientów, bo jedynie ich możemy użyć w czystym CSS, aby wygenerować jakiś wzór. Przynajmniej nie przychodzi mi na myśl coś innego, co może wygenerować jakąkolwiek grafikę. Ale jeśli nałożymy na siebie kilka gradientów i dodamy do każdego z nich odrębną "losową" animację, może coś z tego wyjść. Najlepszym gradientem okazuje się ten wygenerowany poprzez funkcję repeating-radial-gradient, dzięki któremu przy odpowiednich parametrach można stworzyć dosyć gęstą siatkę wielokolorowych okręgów. Wygląda to mniej więcej tak:

Ponieważ pierścienie są blisko siebie i ze względu na ograniczoną gęstość pikseli urządzenia wyświetlającego następuje ich interferencja. Możesz poeksperymentować na http://angrytools.com/css-generator/gradient/. To jeszcze jednak nie to. Jeśli natomiast nałożymy na siebie kilka teł o różnych rozmiarach złożonych z takich gradientów, gdzie kolory w gradientach będą mieć różną transparentość, dodamy efekty mieszania teł (mix-blend-mode), to ostateczny efekt jest naprawdę zaskakujący. Potrzebujemy prostego HTML i CSS:

<div class="noise"></div>

+

.noise {
  width: 250px;
  height: 250px;
  position: relative;
  background-image:
    repeating-radial-gradient(
      ellipse closest-side at 0.2% 0.8%, rgba(255, 255, 255, 1) 0%, rgba(62, 150, 255, 1) 0.2%
    ),
    repeating-radial-gradient(
      ellipse closest-side at 0.2% 1%, rgba(26, 11, 11, .5) 0%, rgba(36, 213, 75, .5) 0.5%
    );
  background-size: 55px 43px;
  filter: grayscale(100%) contrast(150%);
}

.noise:before,
.noise:after {
  content: '';
  width: 100%;
  height: 100%;
  position: absolute;
  background-image:
    repeating-radial-gradient(
      circle farthest-corner at 0.2% 0.4%, rgba(26, 11, 11, .5) 0%, rgba(82, 91, 195, .5) 0.9%
    );
  background-size: 52px 36px;
  mix-blend-mode: difference;
}

.noise:after {
  background-image:
    repeating-radial-gradient(
      circle closest-side at 0.5% 0.3%, rgba(205, 0, 0, .5) 0%, rgba(23, 70, 14, .5) 0.8%
    );
  background-size: 31px 33px;
}

Zresztą zobacz sam (jeśli odnosisz wrażenie, że jednak efekt nie jest taki jak oczekiwany, to czytaj dalej):

Teraz już wiesz, że przykład, do którego dążyliśmy i który podałem na samym początku, to właśnie ten wygenerowany przez CSS. Nieźle, co?

I jeszcze krok dalej. Bo jeśli dodamy animację do każdego z trzech teł i ustalimy dla każdej z nich inną prędkość, to otrzymamy szum jak z telewizora!
CSS może wyglądać tak:

.noise {
/* to co wcześniej + */
  animation: rnd .25s linear infinite;
}

.noise:before,
.noise:after {
/* to co wcześniej + */
  animation: rnd .21s linear infinite;
}

.noise:after {
/* to co wcześniej + */
  animation: rnd .31s linear infinite;
}

@keyframes rnd {
  0% {
    background-position: 10% 11%;
  }
  17% {
    background-position: 23% 1%;
  }
  66% {
    background-position: 7% 47%;
  }
  81% {
    background-position: 31% 25%;
  }
}

Przekroczyć miedzę.

W tym momencie miał zakończyć się ten wpis i pewnie pojawiłby się dzień wcześniej, gdyby nie to, że postanowiłem sprawdzić jak efekt wygląda na przeglądarce mobilnej. Nie spodziewałem się jakiegoś zaskoczenia, ale pomyliłem się. Na przeglądarkach chromopodobnych zobaczyłem zupełnie coś innego. Co ciekawe przy przełączeniu na widok desktopowy efekt wygląda ok. Nie wiem co jest przyczyną, ale nie chodzi tu o pixel ratio, bo się nie zmienia. Wygląda, że problem jest głębszy, co zresztą możesz zauważyć na wspomnianej stronie generatora gradientów. Ustaw tam repeat radial gradient, a jako punkt zatrzymania drugiego koloru wpisz wartość 0.1. Zobaczysz, że nie tylko wzór jest inny, ale gradient nie wypełnia całego obszaru. W mobilnej przeglądarce Firefox jest natomiast w porządku zarówno w widoku mobilnym, jak i desktopowym.

Nie pozostało mi nic innego jak szukać innych metod. W trakcie eksperymentów okazało się, że na przeglądarce desktop (i widoku desktopowym mobilnej) można ten efekt osiągnąć jeszcze prościej. Jeden element, jeden gradient z dosyć dziwną wartością i jak powiadał Doc Brown: you're gonna see some serious s..t.

.noise {
  width: 250px;
  height: 250px;
  position: relative;
/* ustawiamy punkt zatrzymania
   na bardzo małą wartość,
   tu 0.001px
*/
  background-image:
    repeating-radial-gradient(
      ellipse at 0 0, rgb(250, 250, 250) 0, rgb(62, 62, 62) 0.001px);
/* ustalamy eksperymentalnie
   dobrany rozmiar, jeśli
   chcemy statyczny szum
*/
  background-size:53%;
/* animację dodajemy jeśli
   chcemy dynamiczny szum
*/
  animation: rnd 0.25s infinite;
}

@keyframes rnd {
  0% {
    background-size: 49%;
  }
  17% {
    background-size: 51%;
  }
  66% {
    background-size: 66%;
  }
  81% {
   background-size: 41%;
  }
}

Tym razem efekt będzie taki (nadal pamiętaj o widoku desktop):

Nie do końca rozwiązanie może być satysfakcjonujące (ze względu na wspomniany problem), więc jest jeszcze jedna opcja. Wykorzystamy SVG. A dokładnie filtr feTurbulence, czyli wprowadzający powyginaną powierzchnię. W zależności od jego parametru baseFrequency efekt może być gładki, wyglądający jak powierzchnia lekko falującej wody (przy małej częstotliwości/wartości), albo wręcz "zaśmiecony" wzgórkami przypominający właśnie efekt losowości (przy większej wartości). I o ten efekt nam chodzi.

Potrzebujemy kod z naszym filtrem:

<svg>
  <filter id="noise">

<!-- wspomniany filtr, którego wartość baseFrequency
     została dobrana doświadczalnie
-->
    <feTurbulence type="fractalNoise" baseFrequency="0.7" result="A" />

<!-- ten filtr jest tylko po to,
     aby sprowadzić efekt do kolorów
     szarości, zamiast dodatkowego
     filtra grayscale w CSS,
     jeśli chcesz uzyskać kolorowy szum,
     to usuń go, jak i atrybut result
     w filtrze feTurbulence
-->
    <feColorMatrix
        in="A"
          type="matrix"
          values="0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0"
        />
  </filter>
</svg>

+

.noise {
  width: 250px;
  height: 250px;
/* URL z ID naszego
   filtra w SVG
*/
  filter: url(#noise);
}

svg {
  display: none;
}

Tu mamy dwie możliwości sterowania jeśli chcemy uzyskać dynamiczny szum:

  • wartość baseFrequency + ewentualnie wartość numOctaves oraz seed (JS),
  • nałożenie na siebie kilku elementów z takim filtrem i pseudolosowość w postaci animacji zmieniającej położenie, rotację oraz opacity (CSS).

Jeśli dzisiejszy wpis był dla Ciebie zbyt mało losowy, to może warto spróbować szczęścia w jakiejś grze losowej? Do przeczytania!

 

Przydatne linki:
Generator gradientów.
Funkcja repeating-radial-gradient w CSS.
Filtr feTurbulence w SVG.