Hlavní navigace

Zpracování událostí při skriptování výkresů SVG

Pavel Tišnovský 4. 10. 2007

V již desátém článku orientovaném na téma SVG si ukážeme použití programových skriptů vytvářených zejména v JavaScriptu na několika praktických příkladech. Zaměříme se především na způsob zpracování událostí, popis atributů objektu event a také o dvou metodách vhodných pro získání referencí na grafické objekty.

Obsah

1. Rozdíl mezi událostmi onfocusin, onfocusout a onmouseover, onmousemove
2. Demonstrační příklad – použití událostí onmouseover, onmousemove a onmouseout
3. Atributy objektu event
4. Získání reference na objekt představující celý SVG dokument
5. Demonstrační příklad – získání reference na objekt reprezentující SVG dokument
6. Bezpečnější způsob získání reference na objekt představující celý SVG dokument
7. Demonstrační příklad – bezpečný způsob získání objektu SVG dokumentu
8. Obsah další části tohoto seriálu

1. Rozdíl mezi událostmi onfocusin, onfocusout a onmouseover, onmousemove

V předchozí části tohoto seriálu byl uveden demonstrační soubor typu SVG (Scalable Vector Graphics), který obsahoval obsluhu událostí typu onfocusin, onfocusout a také onclick. Vyvolání události onclick je v prakticky všech prohlížečích provedeno po kliku levým tlačítkem myši na grafický objekt. Univerzálnější je událost typu onactivate, která je zavolána i při použití jiného polohovacího zařízení (stylus, klávesová zkratka atd.). Událost onfocusin vzniká tehdy, když nějaký grafický objekt (například kružnice, obdélník, úsečka, text, cesta) získá „fokus/zaměření“, tj. objekt se stane aktivním. V případě, že grafický objekt naopak fokus ztratí, se vygeneruje událost typu onfocusout. Tento typ událostí je specifikován v normě SVG a také bývá podporován ve velkém množství aplikací. Ovšem jednotlivé prohlížeče SVG souborů (včetně webových klientů a jejich doplňků) se liší v tom, kdy přesně událost onfocusin a onfocusout nastane.

Například v Adobe SVG Vieweru, který bývá často používán jako doplněk Microsoft Internet Exploreru, se událost onfocusin vygeneruje ihned poté, co se kurzor myši přesune nad grafický objekt (bez nutnosti kliknutí na tento objekt) a událost onfocusout je vygenerována, když je fokus ztracen, opět bez kliknutí mimo objekt, protože i pozadí výkresu získává fokus při přesunu kurzoru myši. Výsledkem je, že se při zobrazení demonstračního příkladu v prohlížeči Adobe SVG Viewer kružnice zvětšovaly či zmenšovaly s pouhým posunem kurzoru myši. Naproti tomu například prohlížeč integrovaný do webového klienta Opera nebo Mozilla Firefox generuje událost onfocusin až poté, co uživatel objekt nějakým způsobem vybral, typicky kliknutím levým tlačítkem myši (teoreticky je však výběr možné provést i klávesnicí). Událost onfocusout je v těchto prohlížečích generována buď po explicitním výběru jiného grafického objektu nebo po výběru pozadí výkresu (fokus může mít vždy pouze jeden objekt na obrazovce).

V případě, že je zapotřebí reagovat na přejetí kurzoru myši přes nějaký grafický objekt, je výhodnější (resp. přenosi­telnější) místo událostí onfocusin a onfocusout použít události onmouseover, onmousemove a onmouseout. Událost onmouseover je podle specifikace vygenerována v případě, že se kurzor myši přemístí nad nějaký grafický objekt. Většina SVG prohlížečů tuto událost vygeneruje pouze jednou, tj. i když by bylo kurzorem nad objektem neustále pohybováno, není událost dále generována (z tohoto důvodu by asi bylo vhodnější tuto událost nazvat onmousein). Naproti tomu je událost onmousemove vygenerována při každém pohybu kurzoru myši nad daným grafickým objektem. Jak často je událost generována je systémově závislé – může se jednat o pohyb kurzoru o pouhý jeden pixel, ovšem na zatížených počítačích také může docházet k vygenerování události až po delším pohybu. Událost onmouseout je vygenerována (taktéž jednou) v případě, že kurzor myši opustí oblast grafického objektu.

5801
Obrázek 1: První demonstrační příklad ihned po spuštění v SVG prohlížeči

2. Demonstrační příklad – použití událostí onmouseover, onmousemove a onmouseout

V dnešním prvním demonstračním příkladu si ukážeme reakci na události typu onmouseover, onmousemove a onmouseout. Také je ponechána obsluha události onclick, která byla podrobně vysvětlena v předchozí části tohoto seriálu. Pro červenou kružnici umístěnou vlevo nahoře jsou zaregistrovány události onmousemove, onmouseout a onclick, zatímco pro zbylé tři kružnice je provedena registrace událostí onmouseover, onmouseout a onclick. Při vyvolání události onmousemove či onmouseover se zvýrazní obrys kružnic černou křivkou o tloušťce pěti délkových jednotek. Současně se zvětší poloměr dané kružnice o jednu délkovou jednotku. Výsledkem je, že se při přejezdu kurzorem myši nad červenou kružnicí postupně zvětšuje její poloměr (událost onmousemove je generována při každém pohnutí kurzorem), zatímco u ostatních třech kružnic je poloměr zvětšen pouze jedenkrát – při „vjezdu“ kurzoru do oblasti zabrané danou kružnicí. Tento příklad je dobrým testem vlastností jednotlivých prohlížečů SVG souborů i rychlosti jejich reakcí na pohyb myši.

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
                     "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="400"
     height="400"
     viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">

     <!-- JavaScriptový kód pro dynamickou změnu
          vlastností grafických objektů -->
    <script type="text/ecmascript"> <![CDATA[

    // funkce zavolaná po výběru grafického objektu
    function onGfxObjectClick(event)
    {
        var gfxObject=event.target;
        var radius=gfxObject.getAttribute("r");
        if (radius==40)
            gfxObject.setAttribute("r", 20)
        else
            gfxObject.setAttribute("r", 40)
    }

    // funkce zavolaná při najetí kurzorem myši na objekt
    function onMouseOver(event) {
        var gfxObject=event.target;
        // zvětšíme poloměr kružnice o 1
        var radius=gfxObject.getAttribute("r");
        radius++;
        gfxObject.setAttribute("r", radius);
        gfxObject.setAttribute("stroke", "#000")
        gfxObject.setAttribute("stroke-width", 5)
    }

    // funkce zavolaná po vyjetí kurzorem myši z hranic objektu
    function onMouseOut(event) {
        var gfxObject=event.target;
        gfxObject.setAttribute("stroke-width", 0)
    }
    ]]> </script>

    <!-- první grafický objekt -->
    <circle onclick="onGfxObjectClick(evt)"
            onmousemove="onMouseOver(evt)"
            onmouseout="onMouseOut(evt)"
            cx="50" cy="50" r="20"
            fill="#f00"/>

    <!-- druhý grafický objekt -->
    <circle onclick="onGfxObjectClick(evt)"
            onmouseover="onMouseOver(evt)"
            onmouseout="onMouseOut(evt)"
            cx="150" cy="50" r="20"
            fill="#0f0"/>

    <!-- třetí grafický objekt -->
    <circle onclick="onGfxObjectClick(evt)"
            onmouseover="onMouseOver(evt)"
            onmouseout="onMouseOut(evt)"
            cx="50" cy="150" r="20"
            fill="#00f"/>

    <!-- čtvrtý grafický objekt -->
    <circle onclick="onGfxObjectClick(evt)"
            onmouseover="onMouseOver(evt)"
            onmouseout="onMouseOut(evt)"
            cx="150" cy="150" r="20"
            fill="#ff0"/>
</svg> 

5802
Obrázek 2: První demonstrační příklad poté, co se pohybem kurzoru myši zvětšila červená kružnice a žlutá kružnice získala fokus

3. Atributy objektu event

Při každém vyvolání události je obslužné funkci (takzvanému handleru události) předán objekt typu event. Tento objekt obsahuje několik atributů (datových složek), které danou událost blíže popisují. Ve většině případů nás totiž zajímá nejenom fakt, že vůbec nějaká událost nastala, ale i okolnosti jejího vzniku, například přesná poloha kurzoru myši, příznaky stlačení modifikačních kláves typu Shift, Alt apod. Pravděpodobně nejdůležitějším atributem je target, do kterého je uložena reference na grafický objekt, pro který událost vznikla. To, že je tato reference předávána v objektu event, znamená, že jedna obslužná funkce může zpracovávat stejné i rozdílné události vzniklé pro několik grafických objektů – to jsme si ostatně ukázali i v předchozím demonstračním příkladu, ve kterém je například zaregistrována jedna společná obslužná funkce onGfxObjectClic­k() pro všechny čtyři kružnice a rozpoznání konkrétní kružnice je provedeno pomocí výrazu gfxObject=even­t.target.

Mezi další důležité atributy patří souřadnice kurzoru myši či jiného ukazatele, například poloha hrotu stylusu v případě, že jsou SVG soubory zobrazeny na PDA či SmartPhonech (mimochodem, pro tato zařízení je SVG velmi vhodným formátem, který může sloužit i pro tvorbu tenkých klientů nezávislých na HTML či dalších technologiích). Jedná se o atributy screenX a screenY (souřadnice vztažené vzhledem k souřadnicovému systému obrazovky) a také atributy clientX a clientY, které obsahují souřadnice vztažené relativně k rámu SVG výkresu – ten je typicky umístěn uvnitř webového prohlížeče, přičemž se o přepočet souřadnic stará samotný SVG prohlížeč.

Další skupina atributů popisuje stav klávesnice ve chvíli, kdy nějaká událost nastala. Jedná se o atributy charCode (znak, nemusí se jednat pouze o ASCII hodnotu), keyCode (kód klávesy, včetně kurzorových šipek apod.) a atributy popisující stav modifikačních kláves: ctrlKey, altKey, shiftKey, metaKey. V některých případech je význam altKey a metaKey stejný, vhodné je však otestovat chování daného SVG prohlížeče na konkrétních příkladech. Vzhledem k tomu, že některé kombinace modifikačních kláves a myši mohou být použity pro zvětšování či změně polohy výkresu (typicky Ctrl+posun=zoom a Alt+posun=změna polohy výkresu), může se stát, že použitý SVG prohlížeč nevyvolá událost tvořenou touto kombinací, ale sám ji použije pro nadefinovanou činnost. Opět je nutné konkrétní chování prohlížeče otestovat pomocí jednoduchého skriptu – podobně jako v HTML, je i v SVG souborech lze volat funkci alert pro zobrazení zprávy v modálním dialogovém okně.

4. Získání reference na objekt představující celý SVG dokument

V navazujících kapitolách si řekneme, jakým způsobem je možné přistupovat k atributům grafických objektů, a dále si také ozřejmíme, jak lze tyto atributy číst a/nebo měnit přes reference na příslušný grafický objekt. V případě, že byla nějaká funkce zaregistrovaná jako handler události, bylo získání reference na příslušný grafický objekt jednoduché – stačilo použít atribut target objektu event (viz předchozí demonstrační příklad). Ovšem v případě, že je zapotřebí získat referenci na jiný grafický objekt než ten, který vyvolal událost, nebo ve chvíli, kdy nějaká funkce není handlerem události (a není ji tedy předán objekt typu event), je nutné použít nějakou jinou techniku pro získání reference na příslušný grafický objekt. Nejprve je nutné tento grafický objekt jednoznačně identifikovat. To je jednoduché, protože u prakticky všech značek v souborech typu SVG může být uveden atribut id obsahující jméno objektu, například:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
                     "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="400"
     height="400"
     viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">

    <circle id="id_kruh"
            cx="100" cy="100" r="40"
            fill="#f00"/>

    <rect id="id_ctverec"
            x="80" y="80" width="40" height="40"
            fill="#008"/>
</svg> 

V JavaScriptu je pak možné referenci na grafický objekt představující kruh získat způsobem, který mnozí jistě znají ze skriptování HTML či XHTML stránek, tj. přes metodu getElementById() objektu document:

// do proměnné mujKruh se uloží reference na objekt,
// kterému bylo přiřazena identifikace "id_kruh"
var mujKruh=document.getElementById("id_kruh"); 

5. Demonstrační příklad – získání reference na objekt reprezentující SVG dokument

V dnešním druhém demonstračním příkladu je ukázána obsluha události onclick, tj. události, která vznikne po kliku (levým) tlačítkem myši na nějakém grafickém objektu. V obslužné funkci se střídavě zapíná a vypíná zaoblení rohů modrého obdélníka – jednoduše se mění hodnota atributu „rx“, protože norma SVG říká, že pokud není nastaven atribut „ry“, platí rovnost „rx=ry“, tudíž stačí nastavit pouze atribut „rx“. Pro získání reference na objekt reprezentující obdélník je použita metoda getElementById(), tj. není využito možnosti získat tuto referenci přes atribut target objektu event (to není vždy možné, protože v obsluze události můžeme chtít měnit i atributy jiných grafických objektů). Tento demonstrační příklad nemusí být funkční ve všech SVG prohlížečích, protože objekt document nemusí nutně reprezentovat SVG výkres, ale typicky nadřazený HTML/XHTML dokument. Zdrojový soubor druhého demonstračního příkladu má obsah:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
                     "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="400"
     height="400"
     viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">

     <script type="text/ecmascript"> <![CDATA[

    // funkce zavolana po kliku na graficky objekt
    function onCtverecClicked(event)
    {
        var gfxObject=document.getElementById("id_ctverec");
        if (gfxObject.getAttribute("rx")==0)
            gfxObject.setAttribute("rx", 10);
        else
            gfxObject.setAttribute("rx", 0);
    }
    ]]></script>

    <circle id="id_kruh"
            cx="100" cy="100" r="40"
            fill="#f00"/>

    <rect id="id_ctverec"
            x="80" y="80" width="40" height="40"
            fill="#008"
            onclick="onCtverecClicked(evt)" />
</svg> 

5803
Obrázek 3: Kruh a čtverec po zobrazení v prohlížeči SVG

6. Bezpečnější způsob získání reference na objekt představující celý SVG dokument

Na první pohled velmi jednoduchý a účinný způsob výběru objektu, který by předveden v předchozí kapitole, může vést k problémům v případě, že se SVG soubory vkládají do jiných dokumentů, typicky do webových stránek popsaných značkovacími jazyky HTML či XHTML. Mohlo by dojít k nejasnosti, co je konkrétně myšleno objektem nazvaným document, zejména v případě, kdy by na jedné HTML/XHTML stránce bylo zobrazeno více SVG souborů (každý SVG soubor má svůj kořenový objekt nazvaný „document“, i HTML stránka má svůj kořenový objekt pojmenovaný stejně). Z tohoto důvodu je doporučováno (například firmou Adobe pro její SVG prohlížeč Adobe SVG Viewer) použít sice složitější kód, který však dává ve všech případech jednoznačnou referenci na objekt představující celý SVG dokument, tj. dokument popisující jak samotný výkres, tak i přidružená metadata. Poté, co tuto referenci získáme, je možné použít již výše popsanou metodu getElementById() pro přístup k dalším pojmenovaným grafickým objektům.

Doporučovaný postup získání „kořenového“ objektu spočívá ve zjištění, který objekt vyvolal událost onload, tedy událost, jež je zavolána při načítání SVG souboru. Již z normy SVG je zřejmé, že tato událost musí být pro každý validní SVG soubor zavolána, takže se jedná o ideální místo, ve kterém může být prováděna inicializace klíčových částí skriptu, mimo jiné i nastavení užitečných globálních proměnných. Reference na objekt získaná voláním evt.getTarget()­.getOwnerDocu­ment() je uložena do „globální“ proměnné nazvané thisSvgDocument (ve skutečnosti nejde o skutečnou globální proměnnou, ale o proměnnou s rozsahem skriptu resp. obecněji výkresu SVG). Fragment SVG souboru s inicializací proměnné thisSvgDocument může vypadat následovně:

<!-- registrace handleru události onload -->
<svg onload="onDocumentLoad(evt)" ... další parametry...>


// globální proměnná skriptu, ve které se uchovává
// reference na objekt představující celý dokument
var thisSvgDocument=null; // inicializace na null pro
                          // vyvolání výjimky, pokud
                          // svgInitialize() neproběhne

// handler události onload
function onDocumentLoad(evt)
{
    thisSvgDocument=evt.getTarget().getOwnerDocument();
    // nyní již je možné místo potenciálně nefunkčního výrazu
    var objekt1=document.getElementById("id_objektu");
    // použít vetšinou bezproblémové volání
    var objekt2=thisSvgDocument.getElementById("id_objektu");
} 

5804
Obrázek 4: Třetí demonstrační příklad zobrazený v SVG prohlížeči ve výchozím stavu

7. Demonstrační příklad – bezpečný způsob získání objektu SVG dokumentu

Ve třetím demonstračním příkladu je ukázán bezpečný způsob získání reference na objekt, který představuje celý SVG dokument. Tento příklad by měl být funkční ve všech prohlížečích SVG souborů podporujících skriptování pomocí JavaScriptu/EC­MAScriptu. Při načítání dokumentu je vyvolána událost onload pro kořenovou značku <svg>, v jejíž obsluze je zjištěna reference na objekt SVG. Ta je uložena do globální proměnné skriptu, kterou mohou používat (a používají) i další funkce. Zdrojový text tohoto demonstračního příkladu má tvar:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
                     "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="400"
     height="400"
     viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     onload="onDocumentLoad(evt)">

     <script type="text/ecmascript"> <![CDATA[

    // globalni promenna skriptu, ve ktere se uchovava
    // reference na objekt predstavujici cely dokument
    var thisSvgDocument=null; // inicializace na null pro
                              // vyvolani vyjimky, pokud
                              // svgInitialize() neproběhne

    // handler události onload
    function onDocumentLoad(evt)
    {
        thisSvgDocument=evt.getTarget().getOwnerDocument();
    }

    // funkce zavolana po kliku na graficky objekt
    function onCtverecClicked(event)
    {
        var gfxObject=thisSvgDocument.getElementById("id_ctverec");
        if (gfxObject.getAttribute("rx")==0)
            gfxObject.setAttribute("rx", 10);
        else
            gfxObject.setAttribute("rx", 0);
    }
    ]]></script>

    <circle id="id_kruh"
            cx="100" cy="100" r="40"
            fill="#f00"/>

    <rect id="id_ctverec"
            x="80" y="80" width="40" height="40"
            fill="#008"
            onclick="onCtverecClicked(evt)" />
</svg> 

Obrázek 5: Třetí demonstrační příklad zobrazený v SVG prohlížeči po kliku tlačítkem myši na modrý obdélník

8. Obsah další části tohoto seriálu

V následující části seriálu o grafických formátech dokončíme popis standardu SVG a přejdeme k dalším vektorovým grafickým formátům, které sice nejsou tak obecné a skriptovatelné jako SVG, ale zato jsou podporovány v mnoha uživatelských aplikacích.

Našli jste v článku chybu?

5. 10. 2007 3:58

kkl2401 (neregistrovaný)
Ne ne, v ECMAScriptu se gettery/settery urcite nepreferuji, nedava to tam tak dobry vyznam jako treba v te Jave uz jenom proto, ze neexistuje viditelnost (public/private/protected) polozek objektu. getElementById() je neco trochu jineho, to neni normalni getter, spis by se ta metoda dala pripodobnit k sahnuti to nejake (hash)mapy, coz bude ostatne asi jeji obvykla implementace. Tam jde o to, ze dava lepsi smysl umistit na interface Document jednu metodu getElementById, ktera pro predane id vrati…

5. 10. 2007 0:19

Pavel Tisnovsky (neregistrovaný)
Sice jsem to nezkousel (NN4 jsem nikdy nemel v lasce, byl jsem ho nucen docela dlouho pouzivat na SGI, kde nic lepsiho nebylo), ale podle nekterych dokumentu by mel jit Adobacky prohlizec v Netscape rozjet:

http://www.ikaros.cz/node/1330/print
http://interval.cz/clanky/smil-animace-a-prezentacni-efekty/diskuse/
http://www.grafika.cz/art/webdesign/clanek224161341.html

Dneska se IMHO nema moc cenu timto prohlizecem a jeho problemy s kompatibilitou zabyvat, dokonce nekteri autori povazuji prave o…





Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

Lupa.cz: Levný tarif pro Brno nebude. Radní: je to kartel

Levný tarif pro Brno nebude. Radní: je to kartel

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

120na80.cz: Jak oddálit Alzheimera?

Jak oddálit Alzheimera?

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Podnikatel.cz: 1. den EET? Problémy s pokladnami

1. den EET? Problémy s pokladnami

Měšec.cz: Jak levně odeslat balík přímo z domu?

Jak levně odeslat balík přímo z domu?

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

120na80.cz: Horní cesty dýchací. Zkuste fytofarmaka

Horní cesty dýchací. Zkuste fytofarmaka

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Vitalia.cz: Spor o mortadelu: podle Lidlu falšovaná nebyla

Spor o mortadelu: podle Lidlu falšovaná nebyla