Animacje w SVG

Do tej pory zajmowaliśmy się wyłącznie statyczną grafiką. Czas wprowadzić trochę ruchu za pomocą specjalnych narzędzi animacji w SVG oraz skryptów.

Wszelkie właściwości animacji w SVG bazują na języku SMIL (Synchronized Multimedia Integration Language). SMIL jest standardem zalecanym przez W3C do opisu prezentacji multimedialnych z wykorzystaniem XML. Specyfikacja znajduje się na stronie http://www.w3.org/TR/smil20/. Dynamikę do dokumentów SVG można wprowadzić również poprzez języki skryptowe zgodne z ECMAScriptem czyli np. JavaScript. Zacznijmy jednaj od SMIL. W tym systemie określa się początek i koniec wartości atrybutu, kolor, ruch lub transformacje, czas, po którym animacja ma się zaczynać i czas trwania animacji. Do utworzenia animacji potrzebujemy znacznika <animate>. Umieszczamy go wewnątrz definicji tego elementu, który chcemy wprowadzić w ruch. Element <animate> ma atrybut attributeType. Wpisujemy tu odpowiedni parametr – CSS, XML lub auto. Kolejnym atrybutem jest attributeName. Jest to nazwa atrybutu, który będzie zmieniany. Następne parametry to begin i dur. Pierwszy wyznacza moment, w którym wybrany obiekt powinien zostać uaktywniony. Natomiast drugi określa, jak długo element ma pozostawać aktywny. Dysponujemy również atrybutem fill. Jeśli przypiszemy mu wartość freeze, to efektem zakończenia animacji będzie wyświetlenie jej ostatniej klatki. Domyślną wartością dla tego atrybutu jest remove, która powoduje przejście do pierwszej klatki animacji. Ostatnimi parametrami są from i to. Określają one od jakiej do jakiej wartości ma się zmieniać wartość atrybutu podana w attributeName. Zobaczmy teraz wszystko w praktyce.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg" width="250" height="100">
<title>Prosta Animacja</title>
<rect x="10" y="10" width="200" height="20" style="stroke: blue; fill: orange;">
    <animate
        attributeName="width"
        attributeType="XML"
        from="200" to="20"
        begin="0s" dur="5s"
        fill="freeze" />
</rect>
</svg>
Jak widać znacznika <animate> znajduje się wewnątrz prostokąta, który będziemy animować. Ustawienie attributeName="width" oznacza, że zmianie będzie podlegał atrybut width, który jest atrybutem XML (attributeType="XML"). Szerokość prostokąta będzie się zmieniać od 200 do 20, czyli będzie się zmniejszać. Wszystko będzie trwać 5 sekund (begin="0s" dur="5s"). Ponadto po zakończeniu animacji zostanie wyświetlona jej ostatnia klatka (dzięki atrybutowi freeze). Pierwszą i ostatnią klatkę animacji przedstawiają poniższe rysunki. Animacje w SVG Do jednego elementu możemy również zastosować wiele znaczników <animate>. Spójrzmy na poniższy przykład.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 2</title>
<rect x="20" y="20" width="10" height="10"
style="stroke: silver; fill: green; style: fill-opacity: 0.25;">
<animate attributeName="width" attributeType="XML"
from="10" to="250" begin="0s" dur="8s" fill="freeze"/>
<animate attributeName="height" attributeType="XML"
from="10" to="200" begin="0s" dur="8s" fill="freeze"/>
<animate attributeName="fill-opacity" attributeType="CSS"
from="0.25" to="1" begin="0s" dur="3s" fill="freeze"/>
<animate attributeName="fill-opacity" attributeType="CSS"
from="1" to="0.25" begin="3s" dur="3s" fill="freeze"/>
<animate attributeName="height" attributeType="XML"
from="200" to="10" begin="8s" dur="14s" fill="freeze"/>
<animate attributeName="width" attributeType="XML"
from="250" to="10" begin="8s" dur="14s" fill="freeze"/>
</rect>
</svg>
Omówię krótko jak będzie się animował powyższy prostokąt. Atrybut width (atrybut XML) zmienia się od 10 do 250 w czasie 8 sekund. Podobnie atrybut height zmienia się od 10 do 200 w czasie 8 sek. Kolejnym atrybutem podlegającym zmianom jest fill-opacity (jest to atrybut CSS) zmienia się od 0.25 do 1 czyli zwiększa się nieprzezroczystość. Zaczyna się to w zerowej sekundzie i trwa 3 sek. Następnie znowu zmieniamy atrybut fill-opacity, tym razem od 1 do 0.25, zaczynając w trzeciej sekundzie. Animacja ta trwa 3 sek (dur="3s"). Dwa ostatnie znaczniki <animate> odpowiadają za ponowne zmiany atrybutów width i height. Tym razem prostokąt zmniejsza swoje rozmiary, począwszy od 8 sekundy i trwa to 14 sek.
Możemy również animować wiele obiektów na raz. Ilustruje to poniższy przykład, w którym animujemy jednocześnie prostokąt i koło.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 2</title>
<rect x="20" y="20" width="10" height="10"
style="stroke: silver; fill: green; style: fill-opacity: 0.25;">
<animate attributeName="width" attributeType="XML"
from="10" to="250" begin="0s" dur="8s" fill="freeze"/>
<animate attributeName="height" attributeType="XML"
from="10" to="200" begin="0s" dur="8s" fill="freeze"/>
<animate attributeName="fill-opacity" attributeType="CSS"
from="0.25" to="1" begin="0s" dur="3s" fill="freeze"/>
<animate attributeName="fill-opacity" attributeType="CSS"
from="1" to="0.25" begin="3s" dur="3s" fill="freeze"/>
<animate attributeName="height" attributeType="XML"
from="200" to="10" begin="8s" dur="14s" fill="freeze"/>
<animate attributeName="width" attributeType="XML"
from="250" to="10" begin="8s" dur="14s" fill="freeze"/>
</rect>
<circle cx="100" cy="140" r="10"
style="stroke: black; fill: lightblue; style: fill-opacity: 0.25;">
<animate attributeName="r" attributeType="XML"
from="10" to="100" begin="0s" dur="8s" fill="freeze"/>
<animate attributeName="fill-opacity" attributeType="CSS"
from="0.25" to="1" begin="0s" dur="3s" fill="freeze"/>
<animate attributeName="fill-opacity" attributeType="CSS"
from="1" to="0.25" begin="3s" dur="3s" fill="freeze"/>
<animate attributeName="r" attributeType="XML"
from="100" to="10" begin="8s" dur="14s" fill="freeze"/>
</rect>
</svg>
W przypadku prostokąta animacja jest taka sama jak w poprzednim przykładzie. Dodajemy tutaj koło, dla którego zmienia się podczas animacji promień (atrybut r) oraz przezroczystość (fill-opacity). Czas animacji mierzony jest od momentu załadowania dokumentu SVG, aż do chwili kiedy użytkownik opuści stronę. My możemy określić początek i czas trwania poszczególnych części animacji w jeden z następujących sposobów:
  • wartości w godzinach, minutach i sekundach (1:25:31),
  • wartości minut i sekund (5:30),
  • wartości czasowe w jednostkach czasu, gdzie h (godzina), min (minuta), s (sekunda), ms (milisekunda), np. dur="4.5s" lub begin="1min". Zapis liczby i jednostki nie może zawierać spacji.
Można również połączyć początek animacji z początkiem lub końcem innej animacji. Ilustruje to poniższy przykład. Po zakończeniu animacji pierwszego koła rozpoczyna się animacja drugiego.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 4</title>
<circle cx="60" cy="60" r="30" style="fill: #f9f; stroke: gray;">
<animate id="c1" attributeName="r" attributeType="XML"
begin="0s" dur="4s" from="30" to="10" fill="freeze"/>
</circle>
<circle cx="120" cy="60" r="10" style="fill: #9f9; stroke: gray;">
<animate attributeName="r" attributeType="XML"
begin="c1.end" dur="4s" from="10" to="30" fill="freeze"/>
</circle>
</rect>
</svg>
Rysunek poniżej przedstawia kluczowe klatki tej animacji. Animacje w SVG Fragmenty kodu koloru granatowego odpowiadają za to, że animacja drugiego z kół rozpoczyna się wtedy, gdy pierwsze (o id="c1") przestaje się animować. Dokładniej odpowiada za to instrukcja begin="1.end".
Przykładowo aby dodać 2 sekundy opóźnienia dla animacji, która ma wystąpić po innej animacji należy wpisać: begin="inna_animacja.end+2s".
Można również synchronizować animacje przy pomocy atrybutu end, ustawiając czas końca animacji. Spójrzmy na poniższy kod. Poniższa animacja rozpoczyna się 10 sekund po załadowaniu strony i będzie trwała 15 sekund lub kiedy inna_animacja się skończy.
<animate attributeName="width" attributeType="XML"
begin="10s" dur="15s" end="inna_animacja.end"
from="10" to="100" fill="freeze"/>
Oczywiście możemy jako wartość atrybutu end podac również liczbę (np. 10s). Do tej pory nasze animacje odtwarzane były tylko jednokrotnie. Dzięki ustaleniu wartości atrybutu fill na freeze otrzymaliśmy efekt końcowy animacji. Pozostawiając wartość domyślną remove, powrócilibyśmy do pierwszej klatki. Teraz poznamy dwa inne atrybuty, które pozwalają powtarzać animację. Pierwszym z nich jest repeatCount. Jego wartością może być liczba naturalna, określająca ile razy animacja ma być powtarzana. Jeżeli przypiszemy temu atrybutowi wartość indefinite animacja będzie odtwarzana w nieskończoność. Drugi atrybut to repeatDur, który określa jak długo powtórzenie ma trwać (również może przyjąć wartość indefinite). W przykładzie poniżej animacja koła jest powtarzana 3 razy.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 5</title>
<circle cx="60" cy="60" r="10" style="fill: none; stroke: black; fill: green">
<animate attributeName="r" attributeType="XML"
begin="0s" dur="5s" repeatCount="3"
from="10" to="40" fill="freeze"/>
</circle>
</svg>
Tutaj natomiast animacja powtarza się przez 7 sekund, co w rezultacie daje około 1,5 raza.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 5</title>
<circle cx="100" cy="100" r="10" style="fill: #ccf; stroke: black;">
<animate attributeName="r" attributeType="XML"
begin="0s" dur="5s" repeatDur="7s"
from="10" to="45" fill="freeze"/>
</circle>
</svg>
Możemy również łączyć synchronizację animacji z ich powtarzalnością. Załóżmy, że nadaliśmy pierwszej animacji atrybut id="anim1". Ustawimy teraz aby następna animacja rozpoczęła się wtedy gdy pierwsza powtórzy się 2 razy + 2.5 sek. Musimy po prostu dla drugiej animacji wstawić atrybut begin="circle-anim.repeat(2) + 2.5s". Wszystkie z powyższych animacji modyfikowaliśmy za pomocą liczbowych wartości określających czas. Czasami istnieje jednak potrzeba aby użyć innych wartości. Na przykład można sterować widzialnością danego obiektu. Służy do tego znacznik <set>. Potrzebuje on tylko atrybutu to (bez from) i cechy (np. visibility). Ilustruje to przykład. Animacja rozpoczyna się 0.5 sek po załadowaniu strony i trwa 1 sek, aż tekst się pojawi (to="visible").
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 7</title>
<text text-anchor="middle" x="80" y="60" style="visibility: hidden; font-size: 24px">
<set attributeName="visibility" attributeType="CSS"
to="visible" begin="0.5s" dur="1s" fill="freeze"/>
Visible text
</text>
</svg>
Za pomocą zwykłego znacznika <animate> nie możemy manipulować kolorami. Z pomocą przychodzi nam <animateColor>. W wartościach atrybutów from i to tego tagu podajemy po prostu kolory. W poniższym przykładzie zmienia się zarówno kolor wypełnienia, jak i obrysu kwadratu.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 8</title>
<rect x="70" y="60" width="60" height="60"
style="fill: #ff9; stroke: gray; stroke-width: 15;">
<animateColor attributeName="fill"
begin="1s" dur="4s" from="#ff9" to="magenta" fill="freeze"/>
<animateColor attributeName="stroke"
begin="1s" dur="4s" from="green" to="blue" fill="freeze"/>
</rect>
</svg>
Kolejnym ciekawym znacznikiem jest <animateTransform>. Służy on do animacji wykorzystając atrybut translate. W poniższej animacji wykorzystujemy skalowanie (scale) jako typ transformacji.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 9</title>
<circle transform="scale(1)" style="fill: green;" cx="150" cy="150" r="100">
<animateTransform attributeName="transform" attributeType="XML" type="scale" begin="1s" from="1" to="2" dur="10s" fill="freeze" />
</circle>
</svg>
Oczywiście możemy zastosować wiele elementów <animateTransform> do jednego obiektu. Teraz zajmiemy się animacjami po ścieżce. Do implementacji takiej animacji służy znacznik <animateMotion>. Poniżej przedstawiam przykład animacji koła po ścieżce. Jako atrybut w <animateMotion> podajemy ścieżkę (path), po której obiekt ma się poruszać.
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg">
<title>Animacja 10</title>
<path d="M50,125 C 100,25 150,225, 200, 125"
style="fill: none; stroke: blue;"/>
<circle  r="10" style="fill: yellow; stroke: blue;">
<animateMotion
path="M50,125 C 100,25 150,225, 200, 125"
dur="6s" fill="freeze"/>
</circle>
</svg>