Animacje svg

Jozdowska Edyta · 05 Październik 2021

Dawno nie pisałam. Spowodowane jest to wieloma zmianami u mnie. Kilka współprac w trakcie - przez co brakuje czasu.
Jednakże, jestem bardzo zadowolona z ostatniego zlecenia, dlatego też postanowiłam napisać o nim, ku pamięci.

Polegało ono między innymi na wykonaniu animacji svg. Nie jest to nic niezwykłego - obecnie bardzo często na stronach widujemy takie elementy. Pisałam już o nich w poście “SVG i css”. Jednak w takiej formie - ja wykonywałam je po raz pierwszy :smile:

Animacje svg

Animacja miała być na zdarzenie hover myszki i wyglądać między innymi w taki sposób:

Image:  Animacje svg
Animacje svg

Przygotowanie pliku vectorowego

Pierwszą częścią takich animacji jest przygotowanie sobie pliku vectorowego. Do tego celu korzystam z inkscape - podczas zapisywania wybieram opcję czysty svg. W wyniku otrzymuję taki plik xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   width="139.89301"
   height="118.598"
   viewBox="0 0 37.013357 31.379055"
   version="1.1"
   id="svg8">
  <defs
     id="defs2" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <path
     id="Rectangle_11"
     d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z"
     style="fill:#99dde2;stroke-width:0.84015954" />
  <path
     id="Rectangle_49"
     d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z"
     style="fill:#bcedf0;stroke-width:0.84015954" />
</svg>

Usuwam co jest niepotrzebne, czyli

  • wszystkie id
  • dane meta
  • definicje

I otrzymuję taki plik, już po wyczyszczeniu:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="139.89301" height="118.598" viewBox="0 0 37.013357 31.379055">
  <path
    d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z"
    style="fill:#99dde2;stroke-width:0.84015954" />
  <path
    d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z"
    style="fill:#bcedf0;stroke-width:0.84015954" />
</svg>

Jest to grafika svg, zostawiam więc domyślne width i height oraz co jest najważniejsze viewBox, który określa nam obszar naszego obrazu.

Zawierając plik svg w kodzie mamy wpływ na wielkość wyświetlanego obrazu poprzez css - w końcu jest to grafika skalowalana - stąd width i height dla naszego obrazu ma jedynie zastosowanie do poprawnego i pełnego wyświetlenia svg - te wartości są relatywne do wszystkich znaczników zawartych w <svg></svg>. Jeśli zajdzie potrzeba, że obraz ma być większy, możemy targetować w niego css’em - mając pewność, że wszystkie elementy wewnątrz wyświetlą się poprawnie. Takie przynajmniej jest moje zdanie, wynikające z doświadczenia.

Wracając do tematu. Wyrenderowany przez przeglądarkę obraz wygląda tak (nie zamieszczam znacznika <?xml>):

Jeśli mamy zamieszczony plik svg bezpośrednio w kodzie naszej strony, możemy wpływać na niego poprzez naszego css - dzięki czemu dostępne mamy wszystkie triggery css np. :hover lub :active, mamy też możliwość określania kolorów i zachowania poszczególnych elementów obrazu - dzięki czemu możemy skorzystać z animacji css.

Przykład trywialny - zmieńmy kolor naszego kwadratu (dużego) na inny podczas nakierowywania na niego myszką. Wystarczy dodać odpowiednie klasy do naszych znaczników <path/>, oraz ustawić transitions

1
2
3
4
5
6
7
8
9
10
11
12
13
<svg
    class="square"
    width="139.89301" height="118.598"
    viewBox="0 0 37.013357 31.379055">
    <path
        class="bigSquare"
        d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z"
        style="stroke-width:0.84015954" />
    <path
        class="smallSquare"
        d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z"
        style="stroke-width:0.84015954" />
  </svg>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.square
    path
        transition: all .2s linear
        &.bigSquare  
            fill: #99dde2
        &.smallSquare  
            fill: #bcedf0

    &:hover
        path.bigSquare
            fill: red
        path.smallSquare
            fill:pink

Wynikiem powyższego - jak najedziemy kursorem na obraz - duży kwadrat zmieni swój kolor na czerwony, a mały na różowy.

W ten sposób możemy zrobić prawie każdą animację jaką oferuje nam css.

Zatem odwzorujmy gifa z początku artykułu. Potrzebujemy do tego kilka obrazów svg. Ich kolory określę bezpośrednio w css - tak aby mieć jak największą elastyczność przy późniejszych, ewentualnych zmianach.

Sama animacja polega na tym, że duży kwadrat obraca się wokół własnej osi o 90 stopni, natomiast mały kwadrat obracając się wokół własnej osi, zmienia swoją pozycję. Muszę przyznać, że jedynym elementem, jaki sprawił mi tutaj trudność było ustawienie css’a w taki sposób by zachować oś obrotu. Bez odpowiedniego ustawienia, obroty elementów wykonywane były w dość dziwny sposób. Krótkie wyszukiwanie w google i natrafiłam na dwa znaczniki transform-origin i transform-box.

  • transform-origin 1 - odpowia za punkt w jakim “przypięta” zostanie animacja. Zresztą, bardzo fajnie jest to zobrazowane w dokumentacji:
  • transform-box zaś definiuje “kontener”, do którego będzie miała zastosowanie opcja transform i transofrm-origin - przyjmuje ona wartości content-box, border-box, fill-box, stroke-box i view-box. Probowałam jedynie content-box i fill-box. Ta druga odnosi się, jak jej nazwa wskazuje do wypełnienia, w przypadku przykładu - do kwadratu.

Teoria ogarnięta, można przystąpić do pisania css’a. Ja preferuję sass'a z uwagi na jego przejrzystość :smile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.animated-icon
    position: relative
    width: 150px
    height: 150px

    &.smaller
        width: 80px
        height: 80px
    svg
        overflow: visible
        position: absolute
        transition: all .2s
        width: 100%
        height: 100%
        left: 0
        right: 0
        margin: auto
        top: 0
        path,g
            transition: all .2s
            transform-box: fill-box
            transform-origin: center center

To pierwsza część kodu.

Jedyne co wymaga tutaj wyjaśnienia to overflow. Domyślnie svg ma ustawiony overflow na hidden, czyli wszystko co wystaje - schowaj. Ponieważ w niektórych przypadkach, animacja może wychodzić “poza obszar” widzialny svg - warto ją ustawić właśnie na visible, inaczej element może zostać ucięty w którymś momencie animacji.

Druga rzecz - to ustawienie svg na absoluta. Ponieważ rodzic naszej ikony .animated-icon jest relatywny - absolut, będzie relatywny do kontenera przetrzymywanego przez rodzica, czyli do wymiarów .animated-icon.

Kolejnie należy określić zachowanie naszych ikon na zdarzenie :hover

1
2
3
4
5
6
7
8
9
.animated-icon
    &:hover
        .bigSquare
            transform: translateX(15%)
            transform: rotate(90deg)
        .smallSquare
            transform-box: fill-box
            transform-origin: center
            transform: translateX(-70%) rotate(720deg)

Dodajemy kolor na każdą naszą ikonę

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.square
    .bigSquare
        fill: #99dde2

.blue .smallSquare
    fill: #26509b

.green .smallSquare
    fill: #55b555

.light-blue .smallSquare
    fill: #789ad9

.yellow .smallSquare
    fill: #ffed83

.orange .smallSquare
    fill: #f0783c

.purple .smallSquare
    fill: #ab3070

i finalnie mamy taki oto kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div class="animated-icon blue">
<svg width="139.89301" height="118.598" viewBox="0 0 37.013357 31.379055">
    <path class="bigSquare" d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z" style="stroke-width:0.84015954" />
    <path class="smallSquare" d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z" style="stroke-width:0.84015954" />
  </svg>
</div>
<div class="animated-icon green">
<svg width="139.89301" height="118.598" viewBox="0 0 37.013357 31.379055">
    <path class="bigSquare" d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z" style="stroke-width:0.84015954" />
    <path class="smallSquare" d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z" style="stroke-width:0.84015954" />
  </svg>
</div>
<div class="animated-icon light-blue">
<svg width="139.89301" height="118.598" viewBox="0 0 37.013357 31.379055">
    <path class="bigSquare" d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z" style="stroke-width:0.84015954" />
    <path class="smallSquare" d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z" style="stroke-width:0.84015954" />
  </svg>
</div>
<div class="animated-icon yellow">
<svg width="139.89301" height="118.598" viewBox="0 0 37.013357 31.379055">
    <path class="bigSquare" d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z" style="stroke-width:0.84015954" />
    <path class="smallSquare" d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z" style="stroke-width:0.84015954" />
  </svg>
</div>
<div class="animated-icon orange">
<svg width="139.89301" height="118.598" viewBox="0 0 37.013357 31.379055">
    <path class="bigSquare" d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z" style="stroke-width:0.84015954" />
    <path class="smallSquare" d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z" style="stroke-width:0.84015954" />
  </svg>
</div>
<div class="animated-icon purple">
<svg width="139.89301" height="118.598" viewBox="0 0 37.013357 31.379055">
    <path class="bigSquare" d="M -9.9e-7,31.379054 V -6.3470066e-5 H 31.379117 V 31.379054 Z" style="stroke-width:0.84015954" />
    <path class="smallSquare" d="M 25.39887,31.379054 V 19.764689 h 11.614365 v 11.614365 z" style="stroke-width:0.84015954" />
  </svg>
</div>

Całkiem fajne - prawda :smile:

Poniżej jeszcze kilka przykładów wraz z kodem. Tym razem będą animowane trójkąty.

1
2
3
4
5
6
7
8
9
10
11
12
<!-- trójkąty -->
<svg version="1.1" viewBox="0 0 37.013357 31.379055" height="118.598" width="139.89301">
    <path d="M 17.639845,0.89933856 0,31.379054 h 35.219678 z"
        style="fill:#f0d63c;fill-opacity:1;stroke:none;stroke-width:0.6168707px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
    <path d="M 22.391046,9.1342653 9.0599184,31.379054 H 35.219678 Z"
        style="fill:#f03c3c;fill-opacity:1;stroke:none;stroke-width:0.6168707px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
    <path d="m 26.450358,16.166349 -8.829075,15.212705 17.598395,-0.0054 z"
        style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.6168707px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
    <path 
        class="hide fly-in-right" d="m 24.745189,11.8482 -11.378048,19.520644 23.461887,0.01021 z"
        style="fill:#789ad9;fill-opacity:1;stroke:none;stroke-width:0.81052744px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</svg>

Animowane flagi

fly-in-top
fly-in-right
fly-in-up
fly-in-left, move-x
from-center
rotate

Podam jedynie kod sass odpowiedzialny za animację poszczególnych elementów:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
svg
    .hide
        opacity: 0

    .fly-in-top
        transform-box: border-box
        transform: rotate(50deg) translate(10%, -120%)
        transform-origin: right center
    .fly-in-right
        transform: translateX(120%)
        transform-origin: right center
    .fly-in-left
        transform: translateX(-120%)
        transform-origin: left center
    .fade-in-up
        transform: translateY(-50%)
    .from-center
        transform-origin: bottom center
        transform: scale(.6) translateY(35%)
    .rotate
        transform: translate(8%, 0)

    &:hover
        .hide
            opacity: 1
        .fly-in-top
            transform: rotate(0deg) translateX(0)
        .fly-in-right
            transform: translateX(0)
            transition-timing-function: cubic-bezier(.18,.89,.32,1.28)
        .fade-in-up
            transform: translateY(0)
        .move-x
            transform: translateX(-10%)
        .fly-in-left
            transform: translateX(0)
        .from-center
            transform: scale(1) translateY(0%)
        .rotate
            transform: rotate(120deg) translate(10%, -32%)

Połączenie css'a i svg do animacji daje naprawdę dużo możliwości. Ciekawy efekt opisałam jakiś czas temu w poście “SVG i css”

  1. transform-origin Dokumenatcja MDN Web Docs  

Jozdowska Edyta * FullStack Developer

Pisanie kodu jest moją pasją. Zajmuję się tym od przeszło 10 lat, z większą lub mniejszą intensywnością.
Piszę kod w PHP, JS, SCSS i Python. Nie stronię też od poznawania nowych, lub jak kto woli starych rozwiązań jak Jekyll oraz innych języków np. Java.

więcej o mnie