Kurz SVG – animace (praktické ukázky pro pokročilé)

10. listopadu 2004

Pokud jste se nechali pozitivně ovlivnit několika předcházejícími články našeho kurzu SVG, pak už znáte vše potřebné k vytváření pohyblivé a interaktivní 2D grafiky. Nyní máte možnost využít těchto znalostí v pokračování řešených příkladů SVG animací. Opakuji, že mnoho z nich zcela záměrně vychází z existujících vzorů, přičemž samozřejmě není účelem vykrádat cizí nápady, pouze demonstrovat schopnosti SVG formátu.

Pokud je mi znám autor původní grafiky, uvádím jej, případně i odkaz na originální předlohu.

Informace o SVG prohlížečích

Zdá se, že jsem málo zdůrazňoval informace o SVG prohlížečích, tak to na tomto místě napravuji a znovu shrnuji základní fakta. Obdobně jako u formátu SWF (Flash) budete pro zobrazení SVG potřebovat plug-in, jmenovitě Adobe SVG Viewer 3 (ASV3). I když je k dispozici poměrně hodně prohlížečů, animační prvky SVG normy jsou zatím stoprocentně implementovány pouze v ASV2+. ASV existuje pro všechny hlavní platformy, v linuxové verzi bohužel přetrvává několik drobných problémů (viz diskuze k předchozímu článku s ukázkami). Pokud používáte platformu Wintel a prohlížeč Mozilla (FireFox) nainstalujte si nejnovější betaverzi ASV.

Podrobnější informace o prohlížečích, jakožto i mnohé odpovědi typu „proč zrovna SVG“ pro „nevěřící Tomáše“ lze velmi snadno nalézt v paralelní sérii o teoretické stránce SVG zde na Intervalu, zejména v článcích Průvodce SVG – SVG versus Flash, Průvodce SVG nebo Průvodce SVG – konference SVG Open. Rovněž děkuji za všechny diskusní příspěvky i za doplňující informace od znalců SVG.

I přestože je v současné době SVG stále odkázána na plug-iny, stejně jako Flash, přesto má k (X)HTML neskonale blíž, už ze své podstaty značkovacího jazyka, čitelného kýmkoli. Proto by se SVG měla stát běžnou součástí webu. Jsem si jist, že s tímto názorem nejsem osamocen…

Animované hodiny

Tato ukázka bude mnohým čtenářům známá, jelikož pochází z příkladů aplikace Flash. Obraz jsem exportoval přímo z prostředí Flash MX do formátu .AI. Nejpracnější částí byly grafické úpravy v Illustratoru – Macromedia se bohužel neobtěžovala s aktualizací zastaralých exportních filtrů, ty nepodporují ani průhlednost…

Animované hodiny
Animované hodiny (SVG 1, cca 13 kB, SVG 2, cca 13 kB)

Dalším problémem je, že díky zcela rozdílné implementaci stínování vektorových objektů v produktech Adobe oproti Macromedii (lhostejno či Flash nebo Freehand), získáte ve výsledném souboru .AI takové objekty rozkouskované na spoustu diskrétních tvarů – každý krok přechodu = jeden vektorový prvek. To bohužel příliš nepřispívá k optimalizaci výsledného souboru .SVG. Částečně můžete samozřejmě problém velkých souborů řešit komprimací SVG kódu pomocí GZIPu do souborů .SVGZ.

Upravený vektorový obraz byl z Illustratoru exportován do SVG souboru. Dál už to bylo snadné – pro každou ze tří ručiček jsem přidal animační elementy normy SVG (zkrácený výpis):

<g id=“second_hand“>
  <g>
    <path d=“M155.2,201.9h2.2l-2.2-8.4l-2.2,8.4H155.2z“/>
    <path fill=“none“ stroke=“#000000″
      stroke-linecap=“round“ stroke-linejoin=“round“
      d=“M155.2,201.9v13.2″/>
  </g>
  <animateTransform attributeName=“transform“
    type=“rotate“ values=“0 155 215;360 155 215″ dur=“60s“
    fill=“freeze“ additive=“sum“ repeatCount=“indefinite“ />
</g>
<g id=“minute_hand“>
  <g>
    <path d=“…“/>
  </g>
  <animateTransform attributeName=“transform“
    type=“rotate“ values=“0 156 156;360 156 156″ dur=“60min“
    fill=“freeze“ additive=“sum“ repeatCount=“indefinite“ />
</g>
<g id=“hour_hand“>
  <g>
    <path d=“…“/>
  </g>
  <animateTransform attributeName=“transform“
    type=“rotate“ values=“0 156 156;360 156 156″ dur=“12h“
    fill=“freeze“ additive=“sum“ repeatCount=“indefinite“ />
</g>

To je přímočarý postup, který by každého asi napadl jako první – jednoduše nastavit počáteční hodnotu rotace na 0 a konečnou na 360 – poté už jen každou jednotlivou animaci do nekonečna opakovat s nastavenou správnou dobou periody: 1 minuta, 1 hodina a 12 hodin. (Za hodnotou úhlu v atributu values="0 156 156;360 156 156" jsou uvedeny ještě souřadnice středu rotace.)

Takto animované hodiny ovšem působí příliš „počítačově“, nepřirozeně, a proto se v další fázi pokusíme více přiblížit realitě. Inspirací k následující úpravě byly hodiny z dyndns.org (za odkaz děkuji Marku Raidovi). Uvedený příklad má jedinou nevýhodu – používá naskenovaný obraz hodin, což není příliš elegantní.

<g id=“second_hand“>
<g transform=“translate(155 215)“>
  <g transform=“translate(-155 -215)“>
    <path d=“…“/>
  </g>
  <animateTransform attributeName=“transform“ type=“rotate“
    by=“6″ calcMode=“discrete“ dur=“1s“
    accumulate=“sum“ repeatCount=“indefinite“/>
</g>
</g>
<g id=“minute_hand“>
<g transform=“translate(156 156)“>
  <g transform=“translate(-156 -156)“>
    <path d=“…“/>
  </g>
  <animateTransform id=“minuteTransform“
    attributeName=“transform“ type=“rotate“
    calcMode=“linear“ begin=“60s“
    keyTimes=“0.0; 0.03; 1.0″
    values=“0; 6; 6″
    additive=“sum“ accumulate=“sum“
    dur=“60s“ repeatCount=“indefinite“/>
</g>
</g>
<g id=“hour_hand“>
<g transform=“translate(156 156)“>
  <g transform=“translate(-156 -156)“>
      <path d=“…“/>
  </g>
  <animateTransform id=“hourTransform“
    attributeName=“transform“ type=“rotate“
    calcMode=“linear“ begin=“60s“
    keyTimes=“0.0; 0.03; 1.0″
    values=“0; 0.5; 0.5″
    additive=“sum“ accumulate=“sum“
    dur=“60s“ repeatCount=“indefinite“/>
</g>
</g>

V případě sekundové ručičky použijeme calcMode="discrete" a zvětšení úhlu o 6 stupňů (by="6"), abychom získali správně trhaný pohyb každou sekundu. U minutové ručičky je pohyb o 6 stupňů soustředěn do prvního okamžiku každého šedesátisekundového cyklu animace, v řeči matematiky přesně 0,03x60s, což se rovná času 1,8 sekundy.Hodinová ručička se pohybuje se stejným časováním jako minutová, ale pouze o půl stupně. Ještě dodám, že animace výhodně využívají schopnost akumulace animovaných hodnot nastavenou atributem accumulate="sum".

Upozorňuji vás též na šikovný trik, který ještě mnohokráte zužitkujeme, totiž konstrukci se dvěma opačnými transformacemi:

<g transform=“translate(156 156)“>
  <g transform=“translate(-156 -156)“>
    <path … />
  </g>
  <animateTransform … />
</g>

Grafický objekt exportovaný z ilustračních aplikací totiž často nemusí mít bod [0,0] tam, kde potřebujeme. Touto dvojitou transformací dostaneme počátek souřadnic na místo výhodné z hlediska jednoduchosti animace a přitom nezměníme výsledný obraz.

Nakonec bylo ještě nutno nastavit ručičky na skutečný čas. Zde už se JavaScriptu nevyhneme, pomocí manipulace s SVG-DOM nastavíme potřebným prvkům transformační atribut:

<script language=“Javascript“>
<![CDATA[
function nastav_hodiny(evt)
{
  var cas = new Date();
  var sek = cas.getSeconds();
  var min = cas.getMinutes() + sek/60;
  var hod = cas.getHours() + min/60;
  //nastavi aktualni cas (rotaci rucicek)
  var SVGDocument = evt.getTarget().getOwnerDocument();
  SVGDocument.getElementById(„second_hand“).
    setAttribute(‚transform‘,’rotate(‚+(sek*6)+’155,215)‘);
  SVGDocument.getElementById(„minute_hand“).
    setAttribute(‚transform‘,’rotate(‚+(min*6)+’156,156)‘);
  SVGDocument.getElementById(„hour_hand“).
    setAttribute(‚transform‘,’rotate(‚+(hod*30)+’156,156)‘);
  //nastavi pocatek animaci tak,
  //aby doslo k pohybu v celou minutu
  SVGDocument.getElementById(„minuteTransform“).
    setAttribute(‚begin‘, “ + (60-sek) + ‚s‘);
  SVGDocument.getElementById(„hourTransform“).
    setAttribute(‚begin‘, “ + (60-sek) + ‚s‘);
}
]]>
</script>

Technika je velmi podobná takzvanému dynamickému HTML a více do hloubky se na ni podíváme v jiném článku.

Animovaná maska na způsob Macromedie

Tyto tři animace jsou opět inspirované ukázkami od Macromedie. Abych si ušetřil vyrábění ovládacích prvků podobných originálu, rozdělil jsem ukázku do tří SVG souborů.

Animovaná maska na způsob Macromedie
Animovaná maska na způsob Macromedie (SVG 1, cca 3 kB, SVG 2, cca 3 kB, SVG 3, cca 3 kB)

Základem první animace je maska hvězdicovitého tvaru, aplikovaná (v několika duplikátech) na připojený bitmapový obrázek a rozpohybovaná šikovnou kombinací animačních prvků, měnících současně rotaci i měřítko hvězdicovité masky:

<!– animace: otaceni a zmena velikosti –>
<animateTransform attributeName=“transform“
  type=“scale“ values=“.5;2.5;.5″ dur=“14s“
  additive=“sum“ repeatCount=“indefinite“ />
<animateTransform attributeName=“transform“
  type=“rotate“ values=“-29;9;-29″ dur=“4s“
  additive=“sum“ repeatCount=“indefinite“ />

Druhá animace je opět pouhou manipulací s dvěma maskami aplikovanými na obraz.

Ve třetím případě, kdy je objektem posouváno pomocí myši, bylo nutno nasadit JavaScript. Takto jsem připojil ovladač události – skript nastavující žádanou polohu objektu:

<!– nastavime masku a ovladac udalosti –>
<image clip-path=“url(#orez)“ onmousemove=“handler(evt)“
      width=“200″ height=“200″ xlink:href=“a10.jpg“ />

Povšimněte si, že ovladač události je připojen přímo k vymaskovanému obrazu – skript je poté spouštěn pouze tehdy, když se ukazatel přesunuje nad viditelnou (nezamaskovanou) částí obrazu. Původnímu vzoru bychom se ale mohli bez problémů přiblížit, pokud reakci na událost připojíme přímo do kořenového prvku SVG.

Následující velmi jednoduchý kód získá z objektu „event“ polohu ukazatele (evt.clientX, evt.clientY) a nastaví odpovídajícím způsobem transformační atribut:

<script type=“text/ecmascript“>
<![CDATA[
function handler(evt) {
  //obsluha udalosti
  //evt = objekt ‚event‘ s informacemi o udalosti
  //zjisti ukazatel na objekt orezove masky
  var obj = document.getElementById(‚orez‘);
  //a nastavi transformaci masky podle polohy mysi
  obj.setAttribute(„transform“,
    „translate(„+(evt.clientX-120)+“,“+evt.clientY+“)“);
}
]]>
</script>

Nabídka typu „pull-down“

Poměrně často žádaným prvkem webové grafiky jsou nabídky a tak musí konečně i náš kurz přijít se svou troškou do mlýna. Z didaktických důvodů je řešení maximálně zjednodušené.

Náhled interaktivní nabídky
Náhled interaktivní nabídky (SVG 1, cca 4 kB, SVG 2, cca 4 kB, SVG 3, cca 4 kB)

Místo programování skriptů jsem využil animační prvky set. Jako základ implementace klasického „pull-down“ menu jsem zvolil hyperlinky, mimo jiné proto, že jsou funkční i v případě, kdy by prohlížeč „neuměl“ animační elementy. Takto vypadá základní, nultá úroveň nabídky – tedy horní lišta:

<g id=“menu0″ font-family=“Helvetica“
  font-size=“12″ font-weight=“bold“>
  <path opacity=“0.7″
    fill=“url(#line5)“ filter=“url(#MH_Shadow_1)“
    d=“M320,26.4h-298v-24h298V26.4z“/>
  <a id=“m01″ xlink:href=“#“>
    <text transform=“matrix(1 0 0 1 36.2002 18.8999)“>
      SVG text
    </text>
  </a>
  <a id=“m02″ xlink:href=“#“>
    <text transform=“matrix(1 0 0 1 176.2002 18.8999)“>
      Bitmapové efekty
    </text>
  </a>
</g>

Pro funkčnost je důležitý pouze obsah hyperlinkových elementů a, včetně jejich pojmenování atributem id, abychom se na ně v dalším kódu mohli odkazovat.

Teprve v následujícím útržku kódu je skryt celý vtip. V první řadě je to grafická reprezentace „vyskakovací“ části nabídky, ta je ale samozřejmě omezena pouze fantazií grafika. Dále pak dva klíčové prvky set. První z nich skrytou podnabídku zviditelňuje (je aktivován, pokud se ukazatel dostane nad prvek základní úrovně s id="m01" – toto chování určuje atribut begin="m01.mouseover"). Druhý prvek set má za úkol podnabídku skrýt, a to nejen při přesunu ukazatele jinam (se zpožděním 7 s), ale i při aktivaci druhé podnabídky (toto chování zařídíme nastavením atributu begin="m01.mouseout+7; m02.mouseover").

<g id=“menu1″ display=“none“
  font-family=“Helvetica“ font-size=“12″>
  <set attributeName=“display“
    to=“inline“ begin=“m01.mouseover“/>
  <set attributeName=“display“
    to=“none“ begin=“m01.mouseout+7;m02.mouseover“/>
  <path opacity=“0.6″ fill=“url(#line5)“
    d=“M156.7,50.4h-128v-24h128V50.4z“/>
  <path opacity=“0.6″ fill=“url(#line5)“
    d=“M156.7,74.4h-128v-24h128V74.4z“/>
  <a xlink:href=“#“>
    <text transform=“matrix(1 0 0 1 36.2002 42.8999)“>
      Text na křivce
    </text>
  </a>
  <a xlink:href=“#“>
    <text transform=“matrix(1 0 0 1 36.2002 66.8999)“>
      Vytvoření písma
    </text>
  </a>
</g>

Ctěné čtenáře již jistě napadá spousta dalších možností vylepšení. Namátkou mohu jmenovat přidání dalších prvků set, které by mohly měnit třeba barvu odkazů, případně grafického podkladu položky nabídky a podobně, ale to už ponechávám k procvičení za domácí úkol. Vytvořil jsem ještě jednu variantu příkladu, ve které se podnabídka objevuje postupně, animováním její průhlednosti:

<g id=“menu1″ opacity=“0″
  font-family=“Helvetica“ font-size=“12″>
  <animate attributeName=“opacity“
    values=“0;1;1;0″ keyTimes=“0;0.1;0.95;1″
    dur=“7″ fill=“remove“
    begin=“m01.mouseover“ end=“m02.mouseover“
    restart=“whenNotActive“/>
  <path opacity=“0.6″ fill=“url(#line5)“
    d=“M156.7,50.4h-128v-24h128V50.4z“/>
  <path opacity=“0.6″ fill=“url(#line5)“
    d=“M156.7,74.4h-128v-24h128V74.4z“/>
  <a xlink:href=“#“>
    <text transform=“matrix(1 0 0 1 36.2002 42.8999)“>
      Text na křivce
    </text>
  </a>
  <a xlink:href=“#“>
    <text transform=“matrix(1 0 0 1 36.2002 66.8999)“>
      Vytvoření písma
    </text>
  </a>
</g>

Pro zajímavost ještě třetí varianta naznačuje, že graficky mohou položky nabídky vypadat jakkoli, vašemu rozletu nic nebrání. Můžete ke kresbě využít spoustu profesionálních aplikací, třeba jako já Adobe Illustrator, který patří v kreativní grafice bezpochyby k absolutní špičce, případně CorelDraw, nebo jste možná zvyklí pracovat v MS Visiu?

Automobilní demo podle Macromedie

Jak zjistíte o malý kousek dále, následující SVG kód (další animace podle příkladů z balíku Flash MX) je opět dosti primitivní. Obsahuje tři bitmapové obrázky s různými pohledy na animovaný objekt. Každému z nich přiřadíme jeden animační element, který mění jeho průhlednost.

Automobilní demo podle Macromedie
Automobilní demo podle Macromedie (originální SVG, cca 1 kB)

Všechny animační atributy těchto tří prvků jsou stejné, s jedinou výjimkou hodnot v atributu values, které určují časový průběh průhlednosti daného obrázku – ta dosahuje maxima u každého ze tří obrazů v jiné třetině animačního intervalu. Díky (výchozí) lineární interpolaci získáme i efekt postupného prolínání jednotlivých obrazů:

<!– 3 obrazky s animovanou pruhlednosti –>
<image x=“25″ y=“25″ width=“194″ height=“145″ xlink:href=“view1.png“>
  <animate attributeName=“opacity“ begin=“0s“ dur=“15s“
    values=“1;1;0;0;0;0;1″ repeatDur=“indefinite“/>
</image>
<image x=“25″ y=“25″ width=“194″ height=“145″ xlink:href=“view2.png“>
  <animate attributeName=“opacity“ begin=“0s“ dur=“15s“
    values=“0;0;1;1;0;0;0″ repeatDur=“indefinite“/>
</image>
<image x=“20″ y=“25″ width=“194″ height=“145″ xlink:href=“view3.png“>
  <animate attributeName=“opacity“ begin=“0s“ dur=“15s“
    values=“0;0;0;0;1;1;0″ repeatDur=“indefinite“/>
</image>

Jak jsem říkal, nic na tom není…

Nahrávání obrázků

Originální příklad ve Flashi měl demonstrovat nahrávání obrázku pomocí skriptu teprve ve chvíli, kdy bude skutečně potřeba. Ukázka, tak jak jsem ji vytvořil bez skriptů, jen nasazením animačních prvků SVG, funguje trochu jinak. Toto je SVG kód jednoho obrazu včetně jeho přepínání (tl1tl3 jsou identifikátory aktivačních tlačítek):

<g style=“display:none“>
  <set attributeName=“display“ to=“inline“
       begin=“tl1.click“ end=“tl2.click;tl3.click“
       dur=“indefinite“/>
  <animate attributeName=“opacity“
           begin=“tl1.click“ dur=“3s“ values=“0;1″/>
  <image x=“150″ y=“50″ width=“144″ height=“105″
         xlink:href=“image1.jpg“/>
</g>

Obecně odkazuji na analogii s HTML browsery – pokud je obrázek v XML kódu uveden, tak jej prohlížeč prostě načte v rámci vykreslování dokumentu, přičemž záleží jen na konkrétním prohlížeči jak efektivně to udělá (postupné načítání progresivních JPEG obrázků a podobně). Z výše popsaného plyne to, že obrázky v našem dokumentu budou načítány ihned při otevírání grafiky SVG, ještě dříve, než se aktivují animační prvky.

Znalci DHTML jistě vědí, že výsledek podobný vzorovému příkladu lze dosáhnout například změnou atributu src v prvku img pomocí JavaScriptu. V SVG to bude analogicky atribut xlink:href v elementu image.

Mimochodem, prohlížeč od Adobe je poměrně dobře optimalizován a podporuje inkrementální rendering (při vykreslování grafiky nečeká na načtení všech obrázků, písem a podobných datově objemných prvků).

Výslednou grafiku si můžete otestovat, kompletní příklad má zhruba 7 kB.

Interaktivní kostky

Tuto SVG vytvořil Antoine Quint (za inspiraci děkuji Marku Raidovi). Antoine Quint ve svém článku podrobně rozepisuje a vysvětluje postup tvorby této grafiky. Já vám nyní svými slovy zjednodušeně naznačím, jak tato hříčka funguje. Uvidíte, že všechny ty otravné teoretické detaily, kterými jsem vás obtěžoval v předchozích článcích, lze smysluplně a velmi elegantně využít prakticky.

Interaktivní kostky
Interaktivní kostky (originální SVG, cca 1 kB)

Úkolem (viz Flash) bylo vytvořit pole kostiček, které reagují na ukázání ukazatelem myši jakoby „vystřelením“ do vychýlené polohy a následně postupně tlumenými kmity okolo klidové polohy.

Fáze 1: Vytvoření kmitavého pohybu

Užitím nám již dobře známého atributu values="0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0" pro animace souřadnice Y se nasimuluje postupné utlumování kmitů:

<use xlink:href=“#cube“ transform=“translate(307.5, 105.75)“>
  <animate attributeName=“y“ dur=“2s“
    values=“0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0″ />
</use>

Fáze 2: Přidáno startování ukazatelem myši

Přidáním atributu begin="mouseover" definujeme spuštění události (pokud se ukazatel objeví nad konkrétní kostkou):

<use xlink:href=“#cube“ transform=“translate(307.5, 105.75)“>
  <animate attributeName=“y“ begin=“mouseover“ dur=“2s“
    values=“0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0″ />
</use>

Fáze 3: Vylepšení startovací podmínky

Animace, tak jak byla vytvořena v předchozí fázi, má jednu nežádoucí vlastnost – k opětovnému startu animace dojde kdykoli na kostku ukážeme myší. Lepší by bylo, pokud by bylo možné „pérování“ znovu spustit až po utlumení předchozích kmitů. To zařídíme nastavením atributu restart="whenNotActive" – tím dovolíme nový start až po doběhnutí předchozí animace:

<use xlink:href=“#cube“ transform=“translate(307.5, 105.75)“>
  <animate attributeName=“y“ begin=“mouseover“
    restart=“whenNotActive“ dur=“2s“
    values=“0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0″ />
</use>

Fáze 4: Přesné časové řízení animace

Ve čtvrté a páté fázi demonstruje Antoine Quint zvýšení realističnosti pohybu. Nejprve ve čtvrté fázi přidal klíčové časy, aby každá hodnota Y byla dosažena ve fyzikálně správný okamžik. (Každé hodnotě values odpovídá jedna hodnota z atributu keyTimes.)

<use xlink:href=“#cube“ transform=“translate(307.5, 105.75)“>
  <animate attributeName=“y“ begin=“mouseover“
    restart=“whenNotActive“ dur=“2s“
    values=“0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0″
    keyTimes=“0; 0.256; 0.512; 0.615; 0.692; 0.743; 0.794; 0.846; 0.897; 0.923; 0.948; 0.974; 1″ />
</use>

Fáze 5: Přesné řízení zrychlení

Pro řízení zrychlení byla přidána řada hodnot keySplines a nastaven atribut calcMode="spline". Skupina keySplines obsahuje čtveřice řídicích bodů křivek, které určují rychlost změny animované hodnoty. (Jak jsem již uvedl ve výkladové části SVG kurzu, povšimněte si, že atribut keySplines obsahuje skutečně o jednu skupinu hodnot méně, než keyTimes.)

<use xlink:href=“#cube“ transform=“translate(307.5, 105.75)“>
  <animate attributeName=“y“ begin=“mouseover“
    restart=“whenNotActive“ dur=“2s“ calcMode=“spline“
    values=“0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0″
    keyTimes=“0; 0.2564; 0.5128; 0.6154; 0.6923; 0.7436;
              0.7949; 0.8462; 0.8974; 0.9231; 0.9487; 0.9744; 1″
    keySplines=“0 .75 .5 1; .5 0 1 .25; 0 .25 .25 1; .5 0 1 .5;
                0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1;
                0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1″ />
</use>

Kostička již je definována jako symbol a tak zbývá pouze mechanicky naklonovat předchozí kód podle potřeby.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Předchozí článek Virtuální studium - PVT.cz
Další článek templari.cz
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *