Grafika SVG — obszerny przewodnik

> Dodaj do ulubionych

Streszczenie

Jest to obszerny przewodnik po metodach programowania grafiki SVG na przykładzie tworzenia pary animowanych oczu. Dowiesz się jak utworzyć zbiór nadających się do wielokrotnego wykorzystania komponentów graficznych, a także zapoznasz się z niezbędnymi informacjami na temat przekształceń SVG oraz przestrzeni współrzędnych.

SVG to, tak jak HTML czy XML, standardowy format znacznikowy, który generuje skalowalną grafikę wektorową (SVG) wewnątrz przeglądarek internetowych. Grafika wektorowa implementowana jest w postaci prostych kształtów, których ostrość zostaje zachowana przy dowolnym powiększeniu. Dla porównania obrazy rastrowe, np. rysunki z programu Paint, składają się z serii pikseli, które mogą stać się niewyraźne po powiększeniu w wysokiej rozdzielczości. Jeśli więc chcesz móc swobodnie operować na wybranych elementach grafiki, użyj formatu SVG. Grafika SVG generowana jest wewnątrz struktury DOM przeglądarki, zatem każdy komponent graficzny można formatować za pomocą CSS, manipulować nim w JavaScripcie poprzez podstawowe API i sprawić, by był odpowiednio wyświetlany na stronach HTML.

W tej części przewodnika dowiesz się jak korzystać z SVG w obrębie innych, zapewne znanych ci już, podstawowych standardowych technologii sieciowych. Zapoznasz się z istotnymi różnicami oraz wstępnie zaznajomisz się z wieloma funkcjami SVG, które zostały szczegółowo opisanych w dalszych częściach. W tej części dowiesz się przede wszystkim jak stworzyć złożoną interaktywną grafikę, składającą się z nadających się do wielokrotnego wykorzystania komponentów. Nauczysz się zmieniać ich rozmiar oraz umieszczać je w obrębie różnych powierzchni rysunku.

SVG oczy

W ramach tego kursu prześledzisz przykład ilustrujący budowę rysunku pary ruszających się i mrugających oczu.

Definiowanie pola rysunku

Kod SVG można zastosować na kilka sposobów. W przykładach prezentowanych w tym przewodniku jest on osadzony bezpośrednio w kodzie HTML, co pozwala na stosowanie CSS i JavaScriptu zarówno do grafiki SVG jak i do treści HTML. Oto podstawowy kod z elementem svg zawierającym dane grafiki:

<!DOCTYPE html>
<html>
<head>
<title>Przewodnik po grafice SVG — demo</title>
<link href="css/eyeballs.css" rel="stylesheet" />
</head>
<body>
<svg width="600" height="200">
  <title>Oczy</title>
  <desc>Interaktywne demo przedstawiające kilka funkcji grafiki SVG</desc>
</svg>
<script src="js/eyeballs.js"></script>
</body>
</html>

Kiedy tylko zajdzie taka potrzeba, możesz dodać elementy title i desc, aby skomentować kod.

Gałka oczna

Poprzez dodanie poniższej linijki w obszarze svg utworzymy koło. Atrybuty cx i cy pozycjonują jego środek, zaś atrybut r odpowiedzialny jest za rozmiar okręgu względem środka:

<circle id="eyeball" cx="100" cy="100" r="50"/>
SVG koło

Domyślne wypełnienie jest koloru czarnego. Dzięki atrybutowi fill można zmienić kolor tła na biały, a atrybut stroke pozwala doprecyzować kolor krawędzi:

<circle id="eyeball" cx="100" cy="100" r="50" fill="white" stroke="black"/>

iałe koło SVG

By utworzyć tęczówkę i źrenicę, możesz dodać więcej kół i określić ich kolory w atrybucie fill. Na początku zadeklaruj większe okręgi, aby nie przesłoniły mniejszych:


<circle id="eyeball" cx="100" cy="100" r="50" fill="white" stroke="black"/>
<circle id="iris"    cx="100" cy="100" r="25" fill="lightblue"/>
<circle id="pupil"   cx="100" cy="100" r="12" fill="black"/>

Koło z kołami w środku SVG

Stylizacja grafik za pomocą CSS

W powyższym przykładzie id, cx, cy, oraz r są zwykłymi atrybutami, natomiast fill i stroke nazywane są atrybutami prezentacyjnymi. Są to po prostu własności CSS wyrażane jako atrybuty. Można je rozpoznać po nazwach, bo od standardowych własności CSS różnią się tylko zastosowaniem notacjiWielbłądziej zamiast typowej w CSSnotacji z łącznikami-w-dłuższych-nazwach.

Wiele własności CSS, np. fill czy stroke, ma zastosowanie tylko dla treści SVG. Inne zaś, tak jak font-size, można zastosować zarówno do kodu SVG jak i HTML. Niektóre z nich mogą mieć inne nazwy, lecz zachowują się tak jak własności CSS stosowane dla kodu HTML. Własność fill funkcjonuje w SVG w dużym stopniu analogicznie do własności background-color i background-image dla HTML. Ponieważ fill jest własnością CSS, można ją zmodyfikować poprzez atrybut style. Dzięki temu zyska on pierwszeństwo wobec wartości atrybutów prezentacyjnych. Widać to na poniższym przykładzie, w którym kolor oczu zostanie zmieniony na brązowy:

<circle id="iris" cx="100" cy="100" r="25" fill="lightblue" style="fill:brown"/>

Aby oddzielić kod CSS od kodu znacznikowego, arkusze stylów można przenieść do osobnego pliku, podobnie jak robi się w przypadku zwykłego HTML-a. Umieszczenie kodu CSS w pliku eyeballs.css, do którego zostało utworzone odniesienie w kodzie HTML pozwoli uzyskać nieco krótszy kod SVG:

#eyeball { fill: white; stroke: black }
#iris { fill: lightblue }
#pupil { fill: black }
<circle id="eyeball" cx="100" cy="100" r="50"/>
<circle id="iris"    cx="100" cy="100" r="25"/>
<circle id="pupil"   cx="100" cy="100" r="12"/>

W niektórych przypadkach stosowanie arkuszy stylów jest jednak niewskazane, o czym przekonasz się w dalszej części poradnika.

Tworzenie gradientu

Współśrodkowe okręgi są dobrym sposobem na utworzenie krawędzi tęczówki oraz źrenicy, ale nie sprawdzą się przy tworzeniu przekrwienia w kącie oka. Alternatywnym rozwiązaniem może być implementacja całej gałki ocznej w postaci dużego okręgu, wewnątrz którego gradient promienisty utworzy serię współśrodkowych pierścieni:

<circle id="eyeball" cx="100" cy="100" r="150" fill="url(#eyeballFill)" />
<img src="/wp-content/uploads/05502px-svgGrandTour_eyeball_fill.png" alt="Oko - kolejny etap" width="230" height="275" class="alignleft size-full wp-image-13294" />
<radialGradient id="eyeballFill">
  <stop id="inner"           offset="12%"  stop-color="black" />
  <stop id="pupil"           offset="15%"  stop-color="lightblue" />
  <stop id="iris"            offset="27%"  stop-color="lightblue" />
  <stop id="white"           offset="30%"  stop-color="white" />
  <stop id="bloodshotExtent" offset="40%"  stop-color="white" />
  <stop id="outer"           offset="100%" stop-color="pink" />
</radialGradient>
Oko - kolejny etap

Atrybut fill elementu circle wykorzystuje dostępną w CSSfunkcję url(), aby utworzyć odniesienie do identyfikatora elementu radialGradient. Zagnieżdżone w gradiencie elementy stop definiują dość nagłą gradację kolorów od środka w kierunku krawędzi oka — zaczynając od czerni poprzez kolor niebieski, aż do bieli, która łagodniej przechodzi w róż okalający krawędź koła. Gradienty SVG funkcjonują w podobny sposób do gradientów CSS stosowanych do treści HTML. Te ostatnie dostępne są jednak tylko poprzez własność background-image, której nie można zastosować do SVG.

Tworzenie referencji do grafiki

Nowoutworzone koło jest o wiele większe niż właściwa gałka oczna, ponieważ chcemy nim poruszać pod mniejszymi powiekami. (Póki co nie przejmuj się, że kształt jest szerszy od pola rysunku). Nim zbudujemy powieki powinniśmy umieścić w jednym miejscu zdefiniowane już komponenty graficzne. Do elementu svg dodaj zatem obszar defs :

<!DOCTYPE html>
<html>
<head>
<title>Przewodnik po grafice SVG — demo</title>
<link href="css/eyeballs.css" rel="stylesheet" />
</head>
<body>
<svg width="600" height="200">
   <title>gałki oczne</title>
   <desc>Interaktywne demo przedstawiające kilka funkcji grafiki SVG</desc>
   <defs>      <!-- tutaj umieść komponenty -->   </defs></svg>
<script src="js/eyeballs.js"></script>
</body>
</html>

Jeśli przeniesiesz radialGradient do obszaru defs, element circle wciąż będzie się do niego odnosił; jeśli jednak w to samo miejsce przeniesiesz circle, koło nie zostanie wygenerowane.

Dostępny w SVG element use pozwala na wielokrotne wykorzystanie tych samych komponentów graficznych i na umieszczanie ich w obszarach o różnej wielkości. Jeśli umieścimy element circle w obszarze defs i odniesiemy się do niego za pomocą elementu use znajdującego poza elementem defs, grafika zostanie wygenerowana prawidłowo:

<use xlink:href="#eyeball"/>

Funkcjonowanie elementu use może się z początku wydawać nieco zaskakujące. Nie stanowi on bowiem zwykłej kopii obiektu, do którego się odnosi, lecz głęboką referencję. Wszystkie zmiany wprowadzone do prototypu elementu circle i komponentów radialGradient będą następować dynamicznie zawsze, gdy element use będzie się do nich odnosić. Mimo tej elastyczności nie można jednak zmodyfikować żadnych atrybutów obiektów, do których się odnosimy. Istnieje natomiast możliwość dodania opcjonalnych atrybutów prezentacyjnych, które nie zostały jeszcze zdefiniowane.

Jako że element use i jego docelowe elementy znajdują się w różnych częściach drzewa dokumentu, arkusze stylów nie będą, tak jak to zwykle bywa, automatycznie kaskadować do elementu use. Kod CSS można natomiast zastosować do niego osobno. W poniższym przykładzie zastosujemy style przypisane identyfikatorowi docelowego elementu i klasie odwołujących się do niego elementów use:

<style>
#eyeball, .eyeball {
    fill: url(#eyeballFill);
}
</style>
<svg width="200" height="200">
<defs>
<circle id="eyeball" cx="100" cy="100" r="150" />
<defs>
<use xlink:href="#eyeball" class="eyeball" id="eyeball_1"/>
</svg>

Zwróć uwagę, że jeśli wybrany element circle miałby również przypisaną klasę eyeball, element use nie mógłby tego koła zmodyfikować.

Ponadto w elemencie use atrybut href został przyporządkowany do przestrzeni nazw xlink, aby odróżnić go od zwykłego atrybutu HTML o tej samej nazwie używanego bez kwalifikatora. Atrybut xlink:href stanowi inny sposób odnoszenia się do obiektów niż używana w CSS składnia url(), którą zastosowano w przykładzie powyżej.

Powieki

Aby umieścić oko między powiekami, musimy skorzystać ze ścieżki odcięcia, dzięki której dany obiekt wyświetlany jest tylko wówczas, gdy występuje w obrębie innego kształtu. W tym przypadku powieki są ścieżką o dowolnie wybranym kształcie, której atrybut d (definition) pozwala narysować dwie naprzeciwległe krzywe:

<path id="eyelids" d="M 200,100 Q 100,200 0,100 Q 100,0 200,100" />
Powieki SVG

Definicja każdej krzywej zawiera punkty kontrolne, które nie są wyświetlane, lecz mają wpływ na kształt krzywej. Zaczynając od prawego rogu (M, ang. move to — przesuwać się do), punkt kontrolny pierwszej kwadratowej (Q) krzywej umieszczony jest u góry, a krzywa biegnie do lewego rogu. Druga zaś kończy się w prawym, a jej punkt kontrolny umieszczony jest w dolnym rogu.

Oko następny etap

Aby nasz kształt zachowywał się jak ścieżka odcięcia, należy umieścić ścieżkę wewnątrz elementu clipPath. W tym celu niezbędne będzie ponowne wykorzystanie kształtu, zatem warto odnieść się do niego za pomocą referencji use:

<clipPath id="clipEyelid">
    <use xlink:href="#eyelids" class="eyelids" />
</clipPath>

Teraz dodaj własność clip-path, aby zastosować ścieżkę odcięcia do gałki ocznej. W poniższym przykładzie własność ta została przypisana w kaskadowym arkuszu stylów:

.eyeball {
    fill       : url(#eyeballFill);
    clip-path  : url(#clipEyelid);
}
<use xlink:href="#eyeball" class="eyeball" />
Oko z obrysem

Do rozwiązania pozostaje teraz jeszcze tylko jeden problem —powieka zniknęła. Ścieżki odcięcia nie są wyświetlane, zatem musimy odnieść się do nich za pomocą jeszcze jednej referencji. Pierwszy element use widoczny w poniższym kodzie umieszcza gałkę oczną wewnątrz ścieżki odcięcia, zaś drugi generuje samą ścieżkę. Trzeci element use, znajdujący się poza elementem defs , generuje cały obiekt:

<defs>
<g id="eye">
  <use xlink:href="#eyeball" class="eyeball" />
  <use xlink:href="#eyelids" class="eyelids" />
</g>
</defs>
<use xlink:href="#eye" />
Oko z rzęsami

Na tym etapie pracy warto zgrupować nasze dwa elementy graficzne poprzez umieszczenie ich wewnątrz elementu g, dzięki czemu uzyskamy większy obiekt semantyczny o nazwie eye. Do zgrupowanego obiektu można odnieść się za pomocą referencji oraz, o czym przekonasz się w dalszej części, przenieść go lub przekształcić jako jednostkę.

Rzęsy

Narysowanie rzęs wzdłuż powieki będzie wymagało od nas odrobiny kreatywności. Do powieki musimy dodać trzecią referencję.

<g id="eye">
  <use xlink:href="#eyelids" class="eyelashes" />
  <use xlink:href="#eyelids" class="eyelids"   />
  <use xlink:href="#eyeball" class="eyeball"   />
</g>

Klasa eyelashes definiuje wygląd podstawowego kształtu eyelids zupełnie inaczej:

.eyelashes {
    fill              : none;
    stroke            : #ddd;
    stroke-width      : 40;
    stroke-dasharray  : 1,10;
    stroke-linejoin   : bevel;
}

Własność stroke-width pogrubia krawędź w taki sposób, że wychodzi ona poza kontur zarówno do wewnątrz jak i na zewnątrz kształtu. Domyślnie własność stroke ma postać linii ciągłej, jednak można ją zmienić w linię przerywaną. Ustawienie wartości stroke-dasharray na 1,10 sprawi, że na każdy piksel wygenerowany wokół krawędzi kształtu dziesięć zostanie pominiętych. W ten sposób narysujemy pojedyncze rzęsy:

Oko z rzęsami

Własność stroke-linejoin o wartości bevel zapobiega wygenerowaniu rzęs zbyt daleko od rogu oka, gdzie też spotykają się powieki.

Kilka pociągnięć tuszem

Nawet po rozjaśnieniu linii, rzęsy wciąż wydają się zbyt ostre i za cienkie w porównaniu do gałki ocznej. Warto byłoby je nieco zmiękczyć i pogrubić. Filtry SVG dostarczają wielu narzędzi do obróbki grafiki, które można ze sobą łączyć i dopasowywać tak, aby osiągnąć pożądany efekt.

Dodaj element filter do obszaru defs. Będzie on pełnił funkcję kontenera dla różnych komponentów efektów filtra, które domyślnie są zastosowywane jeden po drugim. W poniższym przypadku efekt feGaussianBlur nieregularnie rozproszy piksele (nieco bardziej w pionie aniżeli w poziomie), a feComponentTransfer je przyciemni:

<filter
    id           = "soften"
    x            = "-20"
    y            = "-20"
    width        = "250"
    height       = "250"
    filterUnits  = "userSpaceOnUse"
>
  <desc>Zmiękcza powiekę i rzęsy</desc>
  <feGaussianBlur stdDeviation="1 3" />
  <feComponentTransfer>
      <feFuncR type="linear" slope="0.3"/>
      <feFuncG type="linear" slope="0.3"/>
      <feFuncB type="linear" slope="0.3"/>
  </feComponentTransfer>
</filter>

Ponieważ powieka generowana jest między 0 a 200 pikselem, rzęsy wychodzą nieco poza lewą krawędź pierwotnego pola rysunku. Atrybuty x, y, width i height elementu filter nadają dany efekt na większym obszarze. Nadanie własności filterUnits wartości userSpaceOnUse sprawi, że wykorzystany zostanie układ współrzędnych kształtu, wobec którego jest stosowany filtr. W innym wypadku nadane wartości byłyby interpretowane jako procentowe wartości otaczającego obiekt pola.

Aby nadać określony efekt kształtowi, który wykorzystaliśmy do wygenerowania powiek i rzęs, skorzystamy z własności filter:

#eyelids {
    filter : url(#soften);
}

Tak rzęsy będą prezentować się najpierw po rozmyciu, a następnie po przyciemnieniu:

z filtremz filtrem z filtrami

Przekształcenia i przestrzenie współrzędnych

Na zakończenie zgrupujmy dwa wystąpienia obiektu eye w obiekcie eyes:

<g id="eyes">
  <use xlink:href="#eye"/>
  <use xlink:href="#eye"/>
</g>

Powyższy kod wygeneruje dwa kształty w tym samym miejscu. Aby je rozdzielić, skorzystaj z atrybutu transform. Przesunie on prawe oko nieco w prawo, zaś lewe oko zostanie umieszczone dalej o 300 jednostek:

<g id="eyes">
  <use xlink:href="#eye" id="eyeRight" transform="translate(50,0)"/>
  <use xlink:href="#eye" id="eyeLeft"  transform="translate(350,0)"/>
</g>

Funkcja translate() atrybutu transform pozwala zmienić położenie obiektów, do których utworzono referencję za pomocą elementu use. Przekształcenia można jednak zastosować do niemal każdego elementu graficznego. Przekształcenia SVG cechują się taką samą funkcjonalnością co dwuwymiarowe przekształcenia CSS. Funkcja scale() nadaje obiektowi rozmiar określony przez wartość dziesiętną — 1 to obecny rozmiar, 0 zmniejsza obiekt do punktu, a wartości większe od 1 powiększają grafikę. Funkcja rotate() przyjmuje miarę kąta deg lub rad, o którą obiekt zostanie obrócony. Funkcje skewX() i skewY() przyjmują kąt, o który obiekt zostanie przekrzywiony w poziomie lub w pionie, a funkcja translate() zmienia pozycję obiektu.

Semantycznie zgrupowane oczy zostaną wygenerowane poprzez pojedynczy element use umieszczony poza obszarem elementu defs:

<use xlink:href="#eyes"/>
Dwoje oczu

Jeśli będziemy chcieli wyświetlać oczy wraz z innymi elementami interfejsu, konieczna może okazać się zmiana rozmiaru naszej grafiki. Pierwotny element svg określał, że ma być ona wyświetlana w obrębie prostokąta o wymiarach 600×200 pikseli:

<svg width="600" height="200">
Dwoje oczu w ramce

Co jednak jeśli taki rozmiar grafiki okaże się za duży na nasze potrzeby? Pomniejszenie elementu przy użyciu atrybutów CSS width i height sprawi, że nowe wymiary nie będą odpowiadać wartościom określonym wewnątrz grafiki:

<style>
svg {
    width  : 300px;
    height : 100px;
}
</style>
    <!-- ...or... -->
<svg width="300" height="100">
Pół oka

Rozwiązanie stanowi zdefiniowanie własnego pola za pomocą atrybutu viewBox. W ten sposób zadeklarujemy zbiór abstrakcyjnych jednostek, które będą wykorzystywane jedynie wewnątrz danej grafiki. Nie będą one w żaden sposób powiązane z zewnętrznym układem współrzędnych grafiki, do którego odnoszą się atrybuty SVG width i height:

<svg width="300" height="100" viewBox="0 0 600 200">
viewbox

Jeśli kształt elementu nie będzie pasował do obszaru widoku, w którym wyświetlana jest grafika, przyjdzie nam z pomocą atrybut preserveAspectRatio. W poniższym przykładzie wartość xMidYMid atrybutu wyśrodkuje element i pomniejszy go do odpowiedniego rozmiaru — tak, aby cała grafika mogła zostać wyświetlona:

<svg width="100" height="100" viewBox="0 0 600 200" preserveAspectRatio="xMidYMid">

Wprawianie oczu w ruch

Do rozwiązania pozostał tylko jeden problem — nasza grafika tak naprawdę nic nie robi. Chcielibyśmy natomiast, aby nasze oczy się poruszały.

W wielu przypadkach do animacji własności numerycznych i kolorów w SVG możemy skorzystać z technik CSS. Oto przykład, jak stopniowo zmienić kolor oka, przełączając się pomiędzy reprezentującymi punkty gradientu klasami blue i brown:

 .blue  { stop-color   : lightblue; }
 .brown { stop-color   : brown; }
 stop {
    transition         : all 5s;
    -webkit-transition : all 5s;
    -moz-transition    : all 5s;
 }
Czerwone oczy SVG

Ten znany nam sposób nie zadziała jednak w przypadku atrybutów SVG. SVG ma bowiem swój własny mechanizm (oparty na standardzie SMIL) wykorzystywany do animacji wartości atrybutów. Z jego pomocą sprawimy, że oko spojrzy w bok.

Na początek spróbujmy poruszyć oko statycznie za pomocą opisanego powyżej przekształcenia translate():

<circle id="eyeball" cx="100" cy="100" r="50" transform="translate(50,0)"/>
Zez w prawo

Zdecydowanie nie o to nam chodziło. Powyższy kod przesunął całą gałkę oczną, łącznie ze ścieżką odcięcia, zza której grafika ma być generowana. Spróbujmy zamiast tego zmodyfikować atrybut cx, który określa pozycję środka koła:

<circle id="eyeball" cx="150" cy="100" r="50"/>
Poprawiony zez w prawo

Tak jest o wiele lepiej. Teraz przypiszmy atrybutowi cx jego pierwotną wartość. Aby oko poruszało się dynamicznie, musimy umieścić element animate wewnątrz obiektu eyeball, którego atrybut chcemy zmodyfikować:

<circle id="eyeball" cx="100" cy="100" r="150" >
    <animate
        id             = "glanceStart"
        attributeType  = "XML"
        attributeName  = "cx"
        begin          = "1s"
        dur            = "0.5s"
        from           = "100"
        to             = "150"
    />
</circle>

Przyjrzyjmy się działaniom poszczególnych atrybutów:

  • attributeType zaznacza, że wartość, którą manipulujemy jest atrybutem XML, a nie własnością CSS.
  • attributeName zawiera nazwę atrybutu, który ma zostać zmodyfikowany — w tym przypadku jest to cx.
  • begin określa czas, po jakim zostanie wykonana animacja — w tym przypadku jest to 1 sekunda. Przypisanie wartości 0s wywoła animację natychmiast. (Jeśli pominiesz ten atrybut, animacja nie zostanie wykonana domyślnie, jednak możesz nią sterować przy pomocy JavaScriptu w opisany poniżej sposób).
  • dur określa czas trwania animacji — w tym przypadku jest to pół sekundy.
  • from i to określają wartości, w obrębie których ma nastąpić przejście animacji. W tym przypadku gałka oczna zostanie przesunięta o 50 jednostek w prawo.

Jak widać, w wyniku animacji oko porusza się w prawo, po czym natychmiastowo wraca do swojej pierwotnej pozycji. Możemy w tym miejscu skorzystać z atrybutu fill, noszącego niestety taką samą nazwę co własność fill, nadająca kształtom kolory i gradienty. Dodanie atrybutu fill i przypisanie mu wartości freeze sprawiłoby, że oczy byłyby skierowane w bok także po zakończeniu animacji. Skorzystamy jednak z innej opcji —— dodamy jeszcze inną animację, dzięki której oczy wrócą do swojej pierwotnej pozycji:

<circle id="eyeball" cx="100" cy="100" r="150" >
    <animate
        id             = "glanceStart"        attributeType  = "XML"
        attributeName  = "cx"
        begin          = "1s"
        dur            = "0.5s"
        from           = "100"
        to             = "150"
    />
    <animate
        id             = "glanceEnd"        attributeType  = "XML"
        attributeName  = "cx"
        begin          = "glanceStart.end"        dur            = "0.5s"
        from           = "150"        to             = "100"    />
</circle>

Oprócz odwrócenia wartości from i to czas rozpoczęcia animacji glanceEnd uzależniony jest od momentu, w którym kończy się poprzednia animacja glanceStart. Zauważ, że poza składnią url() i xlink:href, za pomocą których tworzyliśmy odniesienia do obiektów, skorzystamy teraz z trzeciego rodzaju składni — składni kropkowej, która umożliwi nam odniesienie się do identyfikatora glanceStart i wartości jego atrybutu end.

Mruganie

Tak jak w przypadku przejść i animacji CSS, w SVG możesz animować praktycznie każdą wartość numeryczną czy kolor. Ponadto masz możliwość animacji złożonych serii współrzędnych wykorzystanych w definicjach ścieżek. Jedyny warunek to zgodność poleceń dotyczących ścieżek — wówczas mamy pewność, że istnieją korespondujące ze sobą punkty, bez których oczy nie będą mogły mrugać. Dodaj poniższy kod do obiektu eyelids:

<path id="eyelids" d="M 200,100 Q 100,200 0,100 Q 100,0 200,100">
    <animate        id            = "blink"        attributeType = "XML"        attributeName = "d"        from          = "M 200,100 Q 100,200 0,100 Q 100,0 200,100"        to            = "M 200,100 Q 100,100 0,100 Q 100,100 200,100"        begin         = "4s; 6s; 8s; 9s; 11.5s; 13s"        dur           = "0.1s"    /></path>

Atrybut begin określa w powyższym kodzie kilka różnych wartości, zatem animacja zostanie wykonana kilkakrotnie. Pomiędzy wartościami to i from modyfikowane są jedynie pozycje dwóch punktów kontrolnych, które wpływają na kształt krzywej. Animacja zachowuje się więc w poniższy sposób:

Większą kontrolę animacji umożliwi ci JavaScript. Aby z niego skorzystać, wywołaj metodę beginElement() na obiekcie animacji. W tym przykładzie przesunięte zostają współrzędne x/y punktu, w stronę którego pada spojrzenie. Dodajemy także opcjonalny parametr dur, który reguluje czas animacji:

function glanceTo(x,y,dur) {
    dur = (dur || 0.5) + 's'; // Przypisanie trzeciego parametru albo zastosowanie wartości domyślnehj
    var toThere = document.querySelector('#glanceStart');
    var andBack = document.querySelector('#glanceEnd');
    toThere.setAttribute("to", x + " " + y);
    andBack.setAttribute("from", x + " " + y);
    toThere.setAttribute("dur", dur);
    andBack.setAttribute("dur", dur);
    toThere.beginElement();
}

Po wywołaniu poniższej funkcji oczy będą mrugać nieregularnie, w dwu- do pięciosekundowych odstępach:

function blink() {
    var minDelay = 2000;
    var maxExtraDelay = 3000;
    document.querySelector('#blink').beginElement();
    setTimeout(blink, (Math.floor(Math.random() * maxExtraDelay) + minDelay));
}

Interaktywne powiększenie

Załóżmy, że chciałbyś móc powiększyć grafikę poprzez kliknięcie. Skalowanie gałki ocznej będzie problematyczne, ponieważ teraz mamy dwoje oczu, które mogłyby ze sobą kolidować. Możemy natomiast zmienić wymiary obecnego elementu viewBox, aby powiększyć wybrany obszar.

Odnośniki, podobnie jak w HTML-u, tworzy się przy użyciu elementu a, jednak atrybut xlink:href musi być przyporządkowany do odpowiedniej przestrzeni nazw. Ponadto element a nie może opakować zwykłego tekstu, a jedynie inne elementy SVG. Ten link zawiera kontener dla obydwu oczu, zatem kliknięcie któregokolwiek z nich aktywuje odnośnik:

<a xlink:href="#zoomIn">
  <use xlink:href="#eyes"/>
</a> [en]
<view id="zoomIn" viewBox="100 50 100 100" />

Przy odnoszeniu się do wewnętrznego elementu view, wartość elementu svg zostaje nadpisana przez wartość jego potomka, element viewBox. W rezultacie obszar zostaje powiększony:

Domyślnie cała gałka oczna jest aktywna, co można jednak odpowiednio skonfigurować. W dokumencie HTML można decydować czy mają być wykonywane procedury obsługi zdarzeń myszy i dotyku dzięki własności pointer-events — jest to przydatne w przypadku wielowarstwowych interfejsów. W SVG jako obszar aktywny dla obsługi tych zdarzeń można wyznaczyć zarówno wnętrze (visibleFill) jak i obrys (visibleStroke) grafiki (albo obie te części na raz, dzięki wartości visiblePainted). Staną się one aktywne jeśli widoczność grafiki będzie włączona (visible) oraz gdy zdefiniowane zostaną wartości fill i stroke. (W odróżnieniu od visibility:hidden ustawienie display:none całkowicie blokuje generowanie elementu).

Tak jak w HTML-u, własność cursor pozwala na wyróżnienie aktywnych obszarów:

#eyelid:hover, .eyelids:hover { cursor: pointer; }
#eyelid, .eyelids { pointer-events: visiblePainted; }

Powiększenie animowane

Istnieją dwa problemy z nawigacją opisanego powyżej powiększenia uzyskanego za pomocą elementu a. Po pierwsze działa ono tylko w plikach SVG, zaś nie w przypadku SVG osadzonego w kodzie HTML. Po drugie powiększenie wykonywane jest gwałtownie — tak samo jak działa przewijanie strony HTML za pomocą elementu a.

Obydwa problemy możemy rozwiązać poprzez przedefiniowanie domyślnej nawigacji. Na początek zdefiniujmy alternatywne elementy docelowe view oraz element animate, co pozwoli nam na przełączanie się pomiędzy atrybutem viewBox każdego elementu docelowego. Przypisanie atrybutowi fill wartości freeze umożliwi zachowanie danego stopnia powiększenia po zakończeniu animacji:

<view id="zoomOut" viewBox="0 0 600 200" />
<view id="zoomIn" viewBox="100 50 100 100" />

<animate
   id            = "zoomNav" 
   attributeType = "XML"
   attributeName = "viewBox" 
   fill          = "freeze" 
   />
 
<a class="zoom" xlink:href="#zoomIn">
  <use xlink:href="#eyes"/>
</a>

W poniższym kodzie JavaScript wywołujemy funkcję preventDefault(), która dezaktywuje domyślną nawigację odnośników o klasie zoom. Zamiast tego wykonana zostanie animacja zmodyfikowana w oparciu o atrybut viewBox docelowego elementu:

var animate, svg; // odwołują się do elementów SVG
var duration = '3s';
<img src="/wp-content/uploads/24svgGrandTour_eyeball_textPath.png" alt="24svgGrandTour_eyeball_textPath" width="250" height="246" class="aligncenter size-full wp-image-13323" />
window.onload = function() {
    svg = document.querySelector('svg');
    animate = document.querySelector('#zoomNav');
    animate.setAttribute('dur', duration);
    animate.setAttribute('from', svg.getAttribute('viewBox'));
    animate.setAttribute('to', svg.getAttribute('viewBox'));
    var links = document.querySelectorAll('a.zoom');
    for (var i = 0, l = links.length; i < l; i++) {
        // zastępuje domyślną nawigację:
        links[i].setAttribute('onclick', 'event.preventDefault()');
        links[i].addEventListener('click', zoomNav);
    }
};
<img src="/wp-content/uploads/25tekst.png" alt="25tekst" width="278" height="280" class="aligncenter size-full wp-image-13324" />
function zoomNav(e) {
    var hash = e.currentTarget.getAttribute('xlink:href');
    var tgt = document.querySelector(hash);
    // wymienia wartości to/from:
    animate.setAttribute('from', animate.getAttribute('to'));
    animate.setAttribute('to', tgt.getAttribute('viewBox'));
    // wykonuje animację:
    animate.beginElement();
    // stosuje klasę CSS, aby łatwiej dostosować wyświetlanie animacji na każdym poziomie powiększenia:
    svg.className = hash.replace(/#/,'');
}

W ostatniej linijce kontenerowi svg zostają przypisane różne klasy. Wykorzystamy je, aby dostosować wyświetlanie opisanych poniżej elementów tekstowych przy różnych poziomach powiększenia.

Dodawanie tekstu

W SVG możesz łączyć grafiki z tekstem, jednak nie zostanie on opakowany wewnątrz pola tak jak ma to miejsce w HTML-u. Zazwyczaj korzysta się z współrzędnych x i y, aby umieścić elementy tekstowe bezpośrednio w obrębie grafiki. Poniższy przykład obrazuje typowe dla SVG umiejscowienie tekstu wzdłuż krzywej:

Element circle jest pozornie opakowany przez element tekstowy. W przypadku kół trudno jednak wyznaczyć punkt początkowy i końcowy, zatem będziemy musieli skorzystać z dość skomplikowanego polecenia path. W ten sposób narysujemy dwie elipsowate, wygięte w łuk krzywe, które będą do siebie naprzeciwległe, a ich punkty początkowe i końcowe znajdować się będą przy lewej krawędzi:

<path id="irisPath" d="M 60,100 A 40,40 0 0 1 140,100 A 40,40 0 0 1 60,100 "/>
<path id="pupilPath" d="M 78,100 A 22,22 0 0 1 122,100 A 22,22 0 0 1 78,100"/>

Dodamy teraz referencję do obiektu labels oraz innych komponentów oka:

<g id="eye">
  <use xlink:href="#eyelids" class="eyelashes" />
  <use xlink:href="#eyelids" class="eyelids"   />
  <use xlink:href="#eyeball" class="eyeball"   />
  <use xlink:href="#labels"/>
</g>

Element textPath sprawi, że część tekstu będzie wyświetlana wzdłuż ścieżki:

<g id="labels">
  <text>
    <textPath xlink:href="#irisPath">Tęczówka</textPath>
  </text>
  <text>
    <textPath xlink:href="#pupilPath">Źrenica</textPath>
  </text>
</g>
z tekstem

Zwykle możesz przesunąć tekst wzdłuż ścieżki poprzez zwiększanie atrybutu startOffset elementu textPath. Możesz także, tak jak w tym przypadku, obrócić okrągły obiekt korzystając z przekształceń CSS:

#irisPath, #pupilPath {
    fill             : none;
    transform-origin : center         ; -webkit-transform-origin : center  ; -moz-transform-origin : center;
}
#irisPath {
    transform        : rotate(120deg) ; -webkit-transform : rotate(120deg) ; -moz-transform : rotate(120deg);
}
#pupilPath {
    transform        : rotate(20deg)  ; -webkit-transform : rotate(20deg)  ; -moz-transform : rotate(20deg);
}

Domyślnie przekształcenia SVG mają swój początek w lewym górnym rogu elementu. Prawidłowy obrót koła osiągniesz zatem tylko poprzez przypisanie własności transform-origin jej domyślnej wartości w CSS50% 50%. Bądź także ostrożny przy łączeniu przekształceń, ponieważ SVG może wejść w konflikt z CSS.

tekst

Na koniec dodamy kod CSS określający wygląd podpisów, których położenie przy dużym powiększeniu będzie zależało od innych elementów. W wyniku przekształceń własności fill-opacity i stroke-opacity podczas wykonywania animacji tekst będzie się pojawiać i zanikać:

text {
    font-size          : 10px;
    text-transform     : uppercase;
    stroke-width       : 0.5;
    stroke             : black;
    stroke-opacity     : 0;
    fill               : red;
    fill-opacity       : 0;
    /* przejście na domyślny poziom powiększenia zajmuje 2 sekundy; brak opóźnienia */
    transition         : all 2s;    -webkit-transition : all 2s;    -moz-transition    : all 2s;
}
 
svg.zoomIn text {
    fill-opacity       : 0.25;
    stroke-opacity     : 0.25;
    /* przejście na duże powiększenie zajmuje 2 sekundy, 2 sekundy opóźnienia */
    transition         : all 2s 2s;    -webkit-transition : all 2s 2s;    -moz-transition    : all 2s 2s;
}

Litery zachowują się identycznie jak ścieżki, zatem własności fill i stroke będą funkcjonować tak samo jak w przypadku innych kształtów.

Masz już dosyć?

zmęczone oczy

Autor: Mike Sierra

Źródło: http://docs.webplatform.org/wiki/svg/tutorials/smarter_svg_overview

Tłumaczenie: Joanna Liana

Treść tej strony jest dostępna na zasadach licencji CC BY 3.0

2 komentarze do “Grafika SVG — obszerny przewodnik”

  1. Genialny poradnik, naprawdę super, że ktoś to tak fajnie opisał. 🙂

  2. przebrnelam przez kawalek… niestety przy kolejnych liniach kodu nie wiem co usuwać, co gdzie wstawiac 🙁

Możliwość komentowania została wyłączona.