Kurz SVG – DOM a JavaScript při programování animací
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 (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 (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.
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 (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.
Mohlo by vás také zajímat
-
Netcat a Ncat
8. prosince 2022 -
Jak zvýšit CTR vašeho e-mail marketingu
9. září 2024 -
Co je to DNSSEC, jak funguje a jak si ho nastavit?
14. srpna 2024
Nejnovější
-
Výkonný a kompaktní: ASOME Max Studio s výjimečným poměrem cena/výkon
11. listopadu 2024 -
Šokující data od Microsoftu: Kyberútoky rostou o stovky procent!
8. listopadu 2024 -
Chcete jedinečnou doménu? Objevte koncovky FOOD, MEME a MUSIC!
7. listopadu 2024 -
OpenAI představilo novou funkci ChatGPT Search
6. listopadu 2024