Kurz SVG – DOM a JavaScript při programování animací

24. listopadu 2004

V tomto článku vás čeká první pomoc při programování (alias skriptování) v prostředí SVG. Získané znalosti si hned vyzkoušíme v několika zajímavých praktických příkladech. Myslíte si například, že znáte všechny výhody, které mají vektorové mapy v SVG formátu? Možná ano, ale i tak se vás pokusím přesvědčit, že SVG mapy toho dovedou ještě více, než jste čekali.

Jelikož ne všechny úlohy lze zvládnout jen s animačními prvky SVG specifikace, podíváme se na možnosti, které nám nabízí skriptování. Už dříve jsem v tomto kurzu několikráte zmiňoval, že zvládáte-li techniku DHTML, jako byste již uměli programovat i SVG. V následujícím textu této podobnosti tak trochu zneužiji a upozorním vás spíše na odlišnosti a specifika SVG, než abych vysvětloval vše od základu.

Pozor, následující text předpokládá, že s JavaScriptem již nějaké zkušenosti máte a že rovněž máte představu o objektové datové struktuře HTML (XML) stránek. V případě potřeby odkazuji na některé z nesčetných textů o DOM (Document Object Model) a DHTML, z nichž řada je dostupná i zde na Intervalu. V tomto textu nyní zmíním jen některé základní a nejčastěji používané konstrukce. Detaily SVG-DOM bez problému dohledáte v dokumentaci k ASV3.

Test aktivního SVG prohlížeče

Hned ze začátku série textů o SVG jste se o Adobe SVG3 prohlížeči mohli dozvědět, že jedním z hlavních rozdílů oproti předchozí verzi je implementace plnohodnotného interpreteru JavaScriptu. Tato informace je pro nás nyní klíčová. Programátoři firmy Adobe tím výrazně zvýšili kompatibilitu prohlížeče napříč všemi platformami a různými webovými prohlížeči. Osobně silně doporučuji jako první krok při spouštění jakýchkoli skriptů v SVG prostředí provést test verze prohlížeče. Dovoluji si vám nabídnout své odladěné řešení:

<script type=“text/ecmascript“>
<![CDATA[
function testAdobe() {
  //created by Martin Hejral, 2003
  //test if Adobe SVG Viewer 3 (ASV3) or greater is present
  if(window.navigator)
    if( (navigator.appName==“Adobe SVG Viewer“)
     && (navigator.appVersion>=“3.0″) )
      return true;
  alert(„PROSIM, nainstalujte novou verzi Adobe SVG prohlizece!!!“);
  return false;
}
]]>
</script>

A hned mohu dodat, že zápis kódu uvnitř SVG souboru se řídí obecnými XML pravidly, proto je nutné programové kódy vždy uzavřít do sekce CDATA, jak jste si všichni určitě povšimli v předcházejícím výpisu.

Nejdůležitější příkazy a postupy

Toto je notoricky známá konstrukce, kterou začíná téměř každý DHTML (a SVG) skript, který pracuje s DOM. Zjistíme takto ukazatel na objekt, se kterým potřebujeme pracovat – ať už číst jeho atributy, nebo něco modifikovat.

//zjisti ukazatel na SVG objekt
var obj = document.getElementById(„nejake_id“);

Adobe doporučuje ke stejnému účelu o něco složitější postup. Ten vznikl především k zamezení možných problémů v případech, kdy skript prováděl webový prohlížeč (v ASV2) a v HTML stránce bylo současně vloženo několik SVG prvků.

<svg onload=“svgInitialize(evt)“ width=“320″ height=“200″>

var svgDocument = null; //globalni promenna
function svgInitialize(evt)
{
svgDocument = evt.getTarget().getOwnerDocument();
var obj = svgDocument.getElementById(„nejake_id“);

}

Vidíte, že namísto toho, aby se využil standardní objekt document, je zjišťován objekt „vlastnící“ ty konkrétní SVG prvky, se kterými zrovna potřebujeme pracovat. (Abych byl zcela přesný, hledáme vlastníka objektu, který byl zdrojem události onload, tedy prvku svg.) Pro každý vložený SVG soubor se totiž vytvoří jeden další objekt document.

Poměrně hojně využívané jsou i následující příkazy, rovněž známé z DHTML:

  • window.status=“text“ – proměnná
  • window.alert(„text“) – funkce

Významné usnadnění práce při programování vám poskytnou funkce zjednodušující práci s atributy SVG elementů – jejich služeb využijete při práci s SVG velmi často:

  • obj.getAttribute( „transform“ )
  • obj.setAttribute( „transform“, „translate(11,22)“ )

Následující výpis ukazuje, jak modifikovat a číst obsah atributu style. Prozatím příliš nedoporučuji používání style z důvodů kompatibility, jelikož prohlížeče méně vyspělé než ASV2 většinou CSS zatím uspokojivě nepodporují.

var svgobj = svgdoc.getElementById(„nejake_id“);
if (svgobj != null)
{
var svgstyle = svgobj.getStyle();
var fill = svgstyle.getPropertyValue(„fill“);
svgstyle.setProperty(„stroke“,“#69F“);
svgstyle.removeProperty(„stroke-width“);
}

K hodnotám atributů uvnitř struktury SVG-DOM se můžeme dostat alternativně také následujícím zápisem:

  • svgobj.attributes.getNamedItem(„nazev_atr“).value

Zvláště v případě animací jsou často použitelné obecné funkce JavaScriptu pro práci s přerušením a časováním:

  • var casovac = window.setTimeout(„nejaka_funkce()“, 50)
  • window.clearTimeout(casovac)

Obsluha událostí

Další oblíbenou činností, kterou jistě budete chtít provozovat také, je obsluha událostí uživatelského rozhraní – tu velmi často využijete při vytváření interaktivních grafických prezentací. Dovolím si shrnout nejdůležitější praktické informace, které k tomu budete potřebovat.

SVG při obsluze událostí používá model shodný s dobře známou normou DOM2-Events, používaný v prohlížečích Netscape a Mozilla (MSIE se trochu odchyluje). Každá obslužná rutina totiž dostane jako parametr speciální objekt event, který nese informace potřebné k reakci na konkrétní událost. Pozor, závazný název tohoto speciálního objektu v ASV je evt (na rozdíl od klasických WWW browserů)!

Jednotlivé položky ve struktuře objektu evt pak jsou již shodné. Ty nejzajímavější si nyní vyjmenujeme:

  • target – zdrojový objekt události
  • currentTarget – aktuální objekt, ke kterému je připojen obslužný skript (event listener)
  • screenX – X v souřadnicovém systému obrazovky
  • screenY – Y v souřadnicovém systému obrazovky
  • clientX – X relativně vzhledem k oknu (rámu) SVG grafiky
  • clientY – Y relativně vzhledem k oknu (rámu) SVG grafiky
  • ctrlKey
  • shiftKey
  • altKey
  • metaKey
  • button
  • keyCode
  • charCode

Animace s využitím DOM a JavaScriptu

A vzhůru do praxe…

Fyzikální pokus – koule a pružiny

Nyní si vyzkoušíme realizaci známého fyzikálního pokusu, kdy koule, zavěšená na pružině, po jejím napnutí a uvolnění kmitá s klesající amplitudou okolo rovnovážné polohy.

Fyzikální pokus - koule a pružiny
Fyzikální pokus – koule a pružiny (originální SVG, cca 5 kB)

V první fázi jsem si v Illustratoru nakreslil jednoduché ztvárnění pružiny (skupina s id="spring") a koule (skupina s id="sphere"). Dovnitř každé skupiny jsem pak dodal animační prvky. Musí být dva, protože v případě pružiny potřebujeme animovat smršťování, naproti tomu koule jen mění polohu…

<g id=“spring“>
  …
  <!– anim. pruziny –>
  <animateTransform attributeName=“transform“
    type=“scale“ values=“1 1;1 0.32;1 1″ dur=“10s“
    additive=“sum“ repeatCount=“indefinite“ />
</g>
<g id=“sphere“>
  …
  <!– anim. koule –>
  <animateTransform attributeName=“transform“
    type=“translate“ values=“0 0;0 -100;0 0″ dur=“10s“
    additive=“sum“ repeatCount=“indefinite“ />
</g>

Definice pohybu pomocí values ovšem ve výchozím (lineárním) interpolačním režimu vede k fyzikálně nesprávnému časovému řízení animovaných hodnot. Abych dosáhl realistické změny zrychlení, která při tomto fyzikálním procesu probíhá, nastavil jsem výpočetní režim na calcMode="spline" a přidal dvě řídicí křivky do atributu keySplines=".5 0 .5 1;.5 0 .5 1". Navíc byly v druhé fázi koule a pružina definovány jako symboly, abychom je mohli „rozmnožit“:

<use x=“50″ xlink:href=“#spring“>
  <!– anim. –>
  <animateTransform
    attributeName=“transform“ type=“scale“
    values=“1 1;1 0.32;1 1″ dur=“10s“
    keySplines=“.5 0 .5 1;.5 0 .5 1″ calcMode=“spline“
    additive=“sum“ repeatCount=“indefinite“ />
</use>
<use x=“50″ xlink:href=“#sphere“>
  <!– anim. –>
  <animateTransform
    attributeName=“transform“ type=“translate“
    values=“0 0;0 -100;0 0″ dur=“10s“
    keySplines=“.5 0 .5 1;.5 0 .5 1″ calcMode=“spline“
    additive=“sum“ repeatCount=“indefinite“ />
</use>

Stále ale zůstává opominut jeden fyzikální aspekt – v reálném světě bude vždy docházet ke ztrátě energie kmitající soustavy a kmity se budou utlumovat. Mohli bychom to udělat způsobem, který použil v podobném příkladu s kostkami Antoine Quint, ale ten je nepřesný a náročný na vypisování množství polohových údajů.

Proto si nyní ukážeme přímý výpočet animačních fází pomocí JavaScriptu. Prosím ctěné čtenáře, nekamenujte mne za nepřesnosti a zjednodušování ve fyzikální stránce věci, středem zájmu stále zůstává vektorová grafika:

//global variables
var amp=50, scale=0.34, time=0, to=-1;
//perform fading animation
function fade() {
  //get pointer to animated objects
  var obj1 = document.getElementById(‚sphere1‘);
  var obj2 = document.getElementById(‚spring1‘);
  var s = y = Math.cos(time/1000);
  //window.status=“time = „+time/1000+“ s“;
  //multiply COS t with amplitude
  y *= amp;
  //shift sphere to base position
  y -= 50;
  //scale spring
  s *= scale;
  //set base position
  s += 0.34 + 0.32;
  time += 50;
  //amplitude and scale decay
  amp = amp*999/1000;
  scale = scale*999/1000;
  //modify SVG graphics
  obj1.setAttribute( „transform“, „translate(0,“+y+“)“ );
  obj2.setAttribute( „transform“, „scale(1,“+s+“)“ );
  //start timer
  to = window.setTimeout(„fade()“, 50);
}

Uvedený kód vykonává zhruba následující. Ihned poté, co zjistí ukazatele na animované objekty, použije funkci cos() k výpočtu animovaných hodnot: y – aktuální poloha koule, s – aktuální natažení pružiny. Jelikož byla pružina nakreslena v plně natažené poloze, byl jsem následně donucen do výpočtu přidat možná poněkud záhadné konstanty (50, 0.34, 0.32). Pak se posuneme po časové ose zvýšením proměnné time, utlumíme amplitudu a aplikujeme vypočtené hodnoty na SVG objekty (setAttribute).

Nakonec nastavíme časovač, který rutinu fade() spustí znovu po 50 ms.

Život brouka

Tato animace opět vychází z příkladu firmy Macromedia, ve kterém je beruška ovládána virtuálním joystickem.

Opět jsem si trochu ulehčil práci, ořezal jsem příklad až na holé jádro a vypustil některé vlastnosti originálu. Pokud jste poctivě dočetli až sem, máte všechny potřebné znalosti, které vám umožní napsat JavaScriptovou rutinu která by například stejně jako v SWF předloze pohybovala řídicí pákou (něco velmi podobného obsahuje i příklad s animací masky obrázku).

Život brouka
Život brouka (originální SVG, cca 18 kB)

Hned první varianta SVG příkladu naznačuje možnosti řešení této interaktivní animace bez použití programování. Bohužel, ovládání berušky pomocí směrového kříže takto nelze stoprocentně realizovat – neexistuje totiž možnost, aby animační prvek po ukončení a při novém startu přičítal rostoucí hodnoty souřadnic k poloze, ve které byla předtím animace ukončena (beruška zastavena), pročež pohyb začíná vždy znovu od počátečních souřadnic daného animačního prvku.

Pozn. aut.: Takže asi podáme návrh na rozšíření normy SVG o paměťový efekt animačních elementů – navrhuji atribut „memory“ s možnými hodnotami „replace“ a „sum“ kvůli konzistentnosti s atributy „additive“ a „accumulate“.

Prozatím vám nabízím řešení pomocí JavaScriptu v druhé verzi příkladu. SVG kód je pro vaše pohodlí poměrně podrobně okomentován.

Tento kousek SVG je zobrazením ovládacího kříže s připojenou rutinou onmouseover="beetleMania(evt)", která je potom aktivována kdykoli se ukazatel dostane nad zobrazení šipek. Při pozorném čtení kódu zjistíte, že ve skutečnosti je kresba neviditelná. Důvod? Původní směrový kříž byl totiž nakreslen jako jedna spojitá vektorová cesta – to by ale neumožnilo jednoduché rozpoznání směru… Aby prvky vůbec reagovaly na události, musí být vykresleny, a jelikož současně chceme vidět původní kresbu, nastavil jsem jim prostě stoprocentní průhlednost pomocí opacity="0".

<g id=“control“ opacity=“0″ onmouseover=“beetleMania(evt)“>
  <path id=“n“ d=“M61,31.4V20.1h5.6L55,0L43.3,20.1h6v11.2″/>
  <path id=“nw“ d=“M41.5,34.8L33,26.2l2.8-2.8L19,18.9l4.4,16.9l2.8-2.8l8.7,8.7″/>
  <path id=“ne“ d=“M75,41.6l8.6-8.6l2.8,2.8L91,18.9l-16.9,4.5l2.8,2.8l-8.5,8.5″/>
  <path id=“e“ d=“M77.9,60.8h11.9v5.8l20.1-11.7L89.8,43.2V49H78.1″/>
  <path id=“w“ d=“M31.8,49H20.2v-5.8L0,54.9l20.2,11.7v-5.8h11.9″/>
  <path id=“se“ d=“M67.7,74.5l9.2,9.2l-2.8,2.8L91,90.9L86.5,74l-2.9,2.9l-9.1-9.1″/>
  <path id=“s“ d=“M49.2,77.5v12.3h-6L55,109.8l11.7-20.1H61V77.4″/>
  <path id=“sw“ d=“M35.3,67.8l-9.1,9.1l-2.8-2.8L19,91l16.9-4.5L33,83.7l9.2-9.3″/>
</g>

Podprogram beetleMania(evt) si snadno zjistí id prvku (evt.target.id), který spustil událost, a podle něj nastaví směr pohybu:

//’evt‘ is standard name of event object
function beetleMania(evt) {
  //get ‚id‘ of control element to determine direction
  var ctrl = evt.target.id;
  switch(ctrl) {
  case ‚n‘: dx=0; dy=-1; mrot=0;
  break;
  case ‚ne‘: dx=1; dy=-1; mrot=45;
  break;
  case ‚e‘: dx=1; dy=0; mrot=90;
  break;
  case ‚se‘: dx=1; dy=1; mrot=135;
  break;
  case ‚s‘: dx=0; dy=1; mrot=180;
  break;
  case ‚sw‘: dx=-1; dy=1; mrot=-135;
  break;
  case ‚w‘: dx=-1; dy=0; mrot=-90;
  break;
  case ‚nw‘: dx=-1; dy=-1; mrot=-45;
  break;
  }
  //stop event bubbling
  evt.stopPropagation();
  return false;
}

Pokud alespoň trochu vládnete JavaScriptem, jistě pro vás nebude problémem cvičně naprogramovat druhou variantu pohybu, kdy se beruška otáčí plynule, nikoli skokově.

Vlastní pohyb (a modifikace SVG-DOM) zajišťuje rutina beetleMove(), opakovaně spouštěná časovačem:

function beetleMove(evt) {
  //get pointer to animated object
  var obj = document.getElementById(‚beetle‘);
  mx += dx; my += dy;
  if(mx < -128) mx = 128;
  else if(mx > 128) mx = -128;
  if(my < -128) my = 128;
  else if(my > 128) my = -128;
  obj.setAttribute(„transform“,
    „translate(„+mx+“,“+my+“) rotate(„+mrot+“)“);
  to = window.setTimeout(„beetleMove()“, 22);
}

Interaktivní železniční mapa ČR

V tomto příkladu si dovolím trošičku vylepšit interaktivní SVG mapu ze serveru Českých drah. Na okraj ještě jednu poznámku k ovládání Adobe SVG prohlížeče – zmenšování či zvětšování měřítka lze provést i myší, pokud podržíte klávesu CTRL, respektive CTRL+SHIFT, a posuv mapy lze realizovat kombinací ALT+myš, jak je správně uvedeno v mapě.

Interaktivní železniční mapa ČR
Interaktivní železniční mapa ČR (velký náhled, cca 55 kB, originální SVG, cca 30 kB)

V rámci našeho SVG kurzu jsem přidal „roll-over“ efekty (pomocí animačních prvků set) a současně „kontextovou“ nápovědu, která ukazuje velké číslo trati (JavaScript).

K modře zbarveným tratím byl cvičně pomocí animačního prvku set dodán zvýrazňovací efekt:

<path class=“fil0 str4″ d=“M2833 3681l-23 106″>
  <set attributeName=“stroke“ to=“magenta“
    begin=“mouseover“ end=“mouseout“/>
</path>

To byla hračka, zbytek už bude trošičku komplikovanější. Využil jsem toho, že SVG Českých drah obsahuje hyperlinky na jízdní řády, přičemž číslo trati je vždy obsaženo v názvu odkazovaného dokumentu – prostě jsem je z atributu xlink:href v objektové struktuře vyextrahoval:

var trat = obj.attributes.getNamedItem(„xlink:href“).value;

V SVG jsem si předem vytvořil textový prvek s id="info", do kterého níže uvedená rutina zapíše číslo trati a současně jej přemístí na správné souřadnice.

Program, který jsem vytvořil, šikovně využívá efektu zvaného event-bubbling – rutinu odchytávající pohyb myši mi pak stačilo umístit do kořene objektového stromu a počkat si, až dotyčná událost probublá až k ní. Pak testuji, zda se jedná o prvek typu a (hyperlink). Může se vám zdát divné, proč netestuji zdrojový objekt události (evt.target), nybrž jeho rodiče (evt.target.parentNode). Je to proto, že událost onmouseover může spustit pouze viditelný objekt – což jsou čáry znázorňující jednotlivé tratě, hyperlinky jsou jim v našem SVG souboru přímo nadřazeny:

<a xlink:href=“../k130.pdf“ target=“_blank“>
  <path class=“fil0 str4″ d=“M-2472 1338l-294 -23″>
  <set attributeName=“stroke“ to=“magenta“
    begin=“mouseover“ end=“mouseout“/>
  </path>
</a>

Další velmi zajímavou vlastností, jež by rozhodně neměla zůstat nepovšimnuta, je zjištění aktuálního zvětšení a posunutí grafiky v SVG Vieweru (nezapomeňme – vektorovou grafiku lze neomezeně zvětšovat bez ztráty kvality) a následný přepočet souřadnic umisťovaného objektu.

//kontextova napoveda, Martin Hejral, 2004
function napoveda(evt) {
  //evt = objekt ‚event‘ s informacemi o udalosti
  //zjisti ukazatel na textovy informacni objekt
  var obj = document.getElementById(‚info‘);
  //zjisti jmeno tagu zdrojoveho objektu
  var tag = evt.target.parentNode.tagName.toLowerCase();
  if(tag == „a“) {
    //extrahuje cislo trati z odkazu
    //na PDF soubor s jizdnim radem
    //a vlozi jej do text. prvku
    //kontextove napovedy
    obj.firstChild.nodeValue =
      evt.target.parentNode.attributes.
      getNamedItem(„xlink:href“).value.substring(4,7);
  }
  //nasledujici data potrebujeme abychom
  //se prizpusobili aktualnimu zvetseni
  //v prohlizeci SVG
  meritko = document.rootElement.currentScale;
  posun = document.rootElement.currentTranslate;
  //nastavi polohu textu podle polohy mysi
  obj.setAttribute(„transform“, „translate(“
      + (evt.clientX-posun.x)/meritko + „,“
      + (evt.clientY-posun.y)/meritko + „)“);
  //pripadne ‚bublani‘ muze pokracovat
  return evt;
}

Toť vše. Jen za sebe dodám, že přesně zde je skrytá velká síla grafiky ve značkovacím jazyce SVG, zde se otevírá spousta možností pro aplikace. Živě si představuji třeba detailní mapu města, kde jsou všechny objekty podrobně popsány ve standardních prvcích title a desc – skript velmi podobný tomu zde představenému pak dává uživateli této mapy možnost velmi pohodlně tyto informace získat…

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

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

Další článek knihovnabbb.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 *