Ten wpis porusza temat animacji w SVG i choć nie jest okraszony tytanicznym wysiłkiem, to jest iskierką nadziei na kontynuację tego tematu również w przyszłości. A póki co, bez bólu wątroby postaram się przemycić na Twój ekran odrobinę prawie prawdziwego ognia. Wiaderko wody nie będzie jednak potrzebne!
Suche gałązki.
Jak wspomniałem będziemy animować SVG, tak więc potrzebujemy czegoś na czym zaprószymy ogień. Użyjemy trzech różnych kształtów, które podpalimy tym samym efektem.
Będzie to płaska ściółka:
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="200" height="40" />
</svg>
Płomień zapałki:
<svg width="100" height="100" viewBox="0 0 30 35" xmlns="http://www.w3.org/2000/svg">
<path d="M13.23 0C9.508 7.934 7.116 12.897 7.116 17.722c0 4.826 2.735 8.736 6.112 8.736 3.378 0 6.112-3.91 6.112-8.736 0-4.825-2.391-9.788-6.112-17.722z"/>
</svg>
Słońce:
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="100" />
</svg>
Jakikolwiek inny kształt możesz wygenerować sam, np. używając programu Inkscape. Mamy więc wzory, ale nie przypominają w żadnym stopniu języków ognia, nawet statycznych. Aby zaczęły przypominać wykorzystamy dwa filtry bazowe: feTurbulence oraz feDisplacementMap. O pierwszym z nich wspominałem już we wpisie dotyczącym generowania zaszumionego tła. Tworzy on kolorową teksturę przypominającą pofalowaną powierzchnię wody. Drugi filtr natomiast wykorzystuje tą teksturę jako mapę przesunięć dla innej tekstury. Możemy po prostu "powyginać" dowolny obraz wejściowy tak jakbyśmy rozwinęli zgniecioną wcześniej kartkę papieru. Oczywiście możliwe są do wygenerowania również inne efekty pofałdowań, bo dla filtra feDisplacementMap możemy wprowadzić inną źródłową teksturę. Filtr będziemy używać taki sam dla trzech kształtów (no może z delikatnymi różnicami), więc pokazuję efekt działania na pierwszym z nich.
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<filter id="fire">
<!-- generujemy naszą powierzchnię
pofałdowań, wynik przechowujemy
w zmiennej turbulence
-->
<feTurbulence
type="turbulence"
baseFrequency="0.05"
numOctaves="2"
result="turbulence"
/>
<!-- rezultat poprzedniego
filtra (in2) używamy
jako mapę przemieszczeń
dla obrazu źródłowego (in)
(prostokąta, bo na nim
nakładamy filtr). Wartość
składowej R posłuży jako
przemieszczenie w poziomie,
a składowej G w poziomie.
Wejściowe fale skalujemy nieco.
-->
<feDisplacementMap
in2="turbulence"
in="SourceGraphic"
scale="50"
xChannelSelector="R"
yChannelSelector="G"
result="displacement"
/>
</filter>
<rect x="0" y="0" width="200" height="40" style="filter: url(#fire)" />
</svg>
Efekt nabiera kolorów.
Zaczyna to jakoś wyglądać, ale na pewno nie na rozpalony efekt. Dodajmy więc kolory. Nie będziemy używać jednolitego koloru, bo nic się nie poprawi, ale dodamy gradient. Prawdziwy ogień jest jasny na dole, a końce języków rozpływając się w powietrzu są ciemne. Użyjemy SVG-owego gradientu. Do wygenerowania dowolnego innego, polecam stronę http://angrytools.com/gradient/:
Dla ściółki użyjemy linearny:
<linearGradient id="lgrad" x1="50%" y1="100%" x2="50%" y2="0%">
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
<stop offset="30%" style="stop-color:rgb(255,102,0);stop-opacity:1" />
<stop offset="50%" style="stop-color:rgb(255,0,0);stop-opacity:0.72" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:0" />
</linearGradient>
Dla zapałki i Słońca radialny:
<radialGradient id="rgrad" cx="50%" cy="50%" r="75%">
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
<stop offset="30%" style="stop-color:rgb(255,102,0);stop-opacity:1" />
<stop offset="50%" style="stop-color:rgb(255,0,0);stop-opacity:0.72" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:0" />
</radialGradient>
Dodatkowo dodałem filtr blur, który rozmywa nasz efekt w kierunku pionowym (wartość 3) i delikatne w poziomym (wartość 1). Dzięki temu krawędzie języków nie będą gwałtownie odcinać się od otaczającego tła, ale wtopią się w nie:
<!-- źródłem jest pofałdowany obraz
uzyskany z filtra feDisplacementMap
-->
<feGaussianBlur in="displacement" stdDeviation="1,3" />
Dmuchać na gorące!
Teraz już widać, że mamy do czynienia z czymś co przypomina ogień, ale chyba nikt nie spotkał się ze statycznym ogniem w przyrodzie, tak więc i tu postaramy się odzworować bardziej jego realistyczny wygląd. Użyjemy elementu ANIMATE.
Element ma następujące, wspólne atrybuty:
- attributeName - nazwa atrybutu elementu SVG, który będziemy animować,
- attributeType - wartości CSS oraz XML, które oznaczają odpowiednio właściwość sterowaną poprzez CSS lub specyficzny atrybut dostępny tylko w SVG,
- from - od jakiej wartości startujemy animację,
- to - jaką wartość będzie mieć ostatnia klatka animacji,
- dur - czas trwania animacji (dużo szersze możliwości definiowania niż w CSS),
- repeatCount - ilość powtórzeń (również niecałkowita) lub pętla (indefinite).
To tylko część atrybutów, bo jest ich sporo więcej, np.: begin, fill, keys, calcMode, values, itd. My będziemy używać spośród tych wyjaśnionych 1, 2, 5 i 6 oraz calcMode i values. Chcemy nadać animacji bardziej losowy efekt, bo przecież języki ognia skaczą w powietrze w sposób przypadkowy, tak więc wstawimy kilka losowych wartości w atrybucie values (nie będziemy używać from i to, bo nie chcemy animacji od-do). calcMode natomiast przyjmuje wartości: linear, spline, paced oraz discrete. Nas interesuje ta ostatnia, bo oprócz losowości chcemy, aby języki skakały pomiędzy wartościami, a nie płynnie przechodziły od jednej do drugiej. Przypomina to stworzenie animacji w CSS z kilkoma klatkami, a nie tylko pierwszą i ostatnią (pamiętasz wpis Baby steps, czyli kroki w animacji CSS?).
Element będzie mieć postać:
<animate
attributeType="XML"
attributeName="seed"
values="22;1;67;123;13;65;99"
dur="0.8s"
calcMode="discrete"
repeatCount="indefinite"
/>
Wartości oddzielone średnikiem posłużą za wartości dla atrybutu seed, czyli początkowego "ziarna" dla generatora pseudolosowego. Przez 0.8 sekundy animacja będzie skakać po naszych wartościach, które będą podstawiane jako wartość seed. Cały element będzie sterować filtrem feTurbulence.
Posklejany SVG dla podpalonej ściółki prezentuje się tak:
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<!-- gradient -->
<linearGradient id="lgrad" x1="50%" y1="100%" x2="50%" y2="0%">
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
<stop offset="30%" style="stop-color:rgb(255,102,0);stop-opacity:1" />
<stop offset="50%" style="stop-color:rgb(255,0,0);stop-opacity:0.72" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:0" />
</linearGradient>
<!-- cały filtr -->
<filter id="fire">
<!-- tekstura pofałdowań -->
<feTurbulence
type="turbulence"
baseFrequency="0.05"
numOctaves="2"
result="turbulence"
>
<!-- animujemy seed -->
<animate
attributeType="XML"
attributeName="seed"
values="22;1;67;123;1500;13;65;879;99;3;345;92;111"
dur="1.2s"
calcMode="discrete"
repeatCount="indefinite"
/>
</feTurbulence>
<!-- wygenerowane pofałdowania
nakładamy na źródłowy obraz
-->
<feDisplacementMap
in2="turbulence"
in="SourceGraphic"
scale="50"
xChannelSelector="R"
yChannelSelector="G"
result="displacement"
/>
<!-- rozmywamy krawędzie ognia -->
<feGaussianBlur in="displacement" stdDeviation="1,3" />
</filter>
<!-- no i nasza ściółka -->
<rect
x="0"
y="0"
width="200"
height="40"
style="filter: url(#fire)"
fill="url(#lgrad)"
/>
</svg>
Podsumowując:
- tworzymy teksturę pofałdowań (feTurbulence),
- animujemy atrybut seed,
- używamy tekstury pofałdowań jako mapy przemieszczeń (feDisplacementMap),
- nakładamy blur (feGaussianBlur),
- cały, złożony filtr oraz gradient dodajemy do obiektu.
I efekty dla poszczególnych kształtów (są dostępne również w "przydatnych linkach"):
Czy dzisiejszy odcinek spowodował rumieńce na Twojej buzi? Jeśli tak, to zapraszam Cię na kolejny wpis. Do przeczytania!
Przydatne linki:
Generator gradientów.
Element animate dla SVG.
Efekt płonącej ściółki.
Efekt płomienia zapałki.
Efekt rozpalonego Słońca.