Navrhujeme doplněk webu v JavaScriptu

JavaScript Objektově orientované programování Navrhujeme doplněk webu v JavaScriptu

V tomto článku si zkusíme naprogramovat doplněk pro interaktivní stránky v JavaScriptu. Náš doplněk bude fungovat plně automaticky, tvořit budeme kulatý progress bar. K vykreslování použijeme canvas s data-* atributy. V tomto článku to nebudu uvádět step-by-step postupem, jak jste asi zvyklí, ale vždy vás pouze nakopnu (snad) správným směrem. Stejně tak článek počítá s tím, že jste studijní typy a dokážete použít google. I kdybyste nechtěli tvořit celý komplet, tak si můžete stáhnout přiložený zdrojový kód (dole pod článkem) a upravit ho tak, aby byl lepší a dokonalejší.

Někde se začít musí

Většina lidí začíná tím, že si představí výsledek. To je sice fajn, ale když pak začínají psát, pořád mají tu představu. Začněte jinak, představte si svůj výsledek, když jste jej ořezali na minimum. Toto se vám bude dělat mnohem lépe a té první představy dosáhnete mnohem rychleji. Stejně tak je dobré začít právě tím základem. Jestliže začnete vykreslováním textu doprostřed, při dodělávání samotného progress baru zjistíte, že to není až zas tak snadné a přitom je.

Začneme samotným skriptem, který bude obsluhovat každý canvas s atributem data-procent. K tomuto účelu nám dobře poslouží metoda document.query­SelectorAll(), která v parametru dostává CSS selektor a vrátí nám pole elementů, které by ovlivnil tento selektor. Pro vybrání všech canvasů s atributem data-procent použijeme selektor canvas[data-procent], po načtení stránky tedy projdeme pomocí querySelectorA­ll("canvas[da­ta-procent]"), který nám vrátí přesně ty canvasy co potřebujeme.

Nyní si vytvoříme objekt, který bude zastupovat náš kulatý progress bar. Objekty jsou v JavaScriptu jeden velký kámen úrazu. Definují se totiž konstruktorem, což je jednak už od pohledu divné, špatně se v tom orientuje a tím se nám občas i změní docela jasné proměnné i nabobtná pomocný kód. Nicméně se na objekty v JavaScriptu podívejme z té druhé stránky, jakmile by se naše progressbary rozrostly, projekt by se automaticky stal natolik složitý, že bez dobrého návrhu bychom se dříve nebo později dostali do bodu, kdy jsme na dosavadním projektu víceméně pouze ztratili čas. Ačkoli tedy s objekty ze začátku budeme zápasit, nebudeme se jim za žádnou cenu vyhýbat.

Objekt v JavaScriptu definujeme ve funkci, tam načteme všechny vlastnosti. Metody se definují přes prototype této třídy. Vlastnosti ukládáme přes klíčové slovo this. Jako jednoduchou ukázku kódu použijeme třídu pro osoby, na které to lze všechno docela pěkně vidět.

function Clovek(jmeno, vek) {
        this.jmeno = jmeno;
        this.vek = vek;

        Clovek.prototype.Pozdrav = function () {
                alert("Jmenuji se " + this.jmeno + " a je mi " + this.vek + " let.");
        }
}

var karel = new Clovek("Karel", 34)
karel.Pozdrav();

Zde vytváříme 34 letého Karla a necháme ho, aby nás pozdravil. V našem doplňku nám bude stačit jen jeden objekt, ten bude reprezentovat právě náš Progressbar. Objekt by mohl mít metody pro aktualizaci stavu a vykreslení, případně by mohl obsahovat metody pro vykreslování jednotlivých částí, čímž se kód zpřehlední. Jednotlivé metody a celkový kód nezapomeňte zdokumentovat, velmi vám to pomůže vyznat se v už tak dost nepřehledných objektech. Pro dokumentaci můžete použít třeba nějakou syntaxi, je jich hodně, můžete použít třeba JSdoc. Zápis JS doc se příliš neliší od dokumentace používané v Javě, naši metodu Pozdrav v člověkovi bychom mohli zdokumentovat následovně:

/**
 * @author Misaz
 * Představí člověka
*/
Clovek.prototype.Pozdrav = function () {
        alert("Jmenuji se " + this.jmeno + " a je mi " + this.vek + " let.");
}

Vykreslování

Vykreslování na plátno je zprostředkováno kontextem, na kterém se volají jednotlivé metody pro vykreslování. Budou vám bohatě stačit metoda arc(), která bere různé parametry a vykreslí část kruhu. Pozor si dávejte na to, že všechny úhly nejsou ve stupních, ale v radiánech. Vzorec pro převod stupňů na radiány je

Radiany = PI / 180 * stupně

Pokud převod budete používat častěji, vložte si to do funkce někde mimo. Velmi zajímavé je si tu funkci vložit přímo do interních Math. V JavaScriptu je to snadné, prostě přiřadíte do požadovaného názvu v Math (vytvoříte si nový, nic nepřepisujete) onu funkci. Tímto zakomponujete vaši funkci do již existujících a čistota kódu je zachována.

Math.deg2rad = function (degrades) {
        return Math.PI / 180 * degrades
}

Aby metoda arc() fungovala podle vašich potřeb, je potřeba si pohrát s cestami. Cestu otevřete metodou beginPath(), ukončíte closePath() a vykreslíte přes fill(). Ta vyplní uzavřenou cestu a stroke vykreslí pouze obrys.

Animace

Abyste docílili efektu animace, musíte v určitém intervalu volat vykreslování a aktualizaci stavu. K tomu můžete použít dvě varianty. Můžete nastavit interval nebo odpočet s tím, že odpočet byste pak museli v každém kroku nastavit nový. Na první pohled se může zdát, že interval je tedy lepší, ale opak je pravdou. Interval běží stále, i když není potřeba – animace přeci nebude nekonečná. Toto sice na běžném moderním počítači s obvykle 2 a více jádrovým procesorem nejde poznat, ale na mobilu se to výrazně negativně projeví na výdrži baterie (v dnešní době velký problém) a u některých telefonů i přehřívání (ale toho se většinou nevyhnete tak či tak). Osobně vám tedy s ohledem ke koncovému uživateli doporučuji metodu odpočtu, nastavíte ji metodou setTimeout(). Pokud jste se i přesto rozhodli pro interval, tak ten nastavíte přes metodu setInterval(). I pro interval je tu ještě jedna možnost, která by řešila problém zbytečných aktualizací a to návratovou hodnotu si někam uložit a v okamžiku konce animace zavolat clearIterval(), do parametru předáte právě proměnnou. Ve výsledku to může vyjít na stejno a je jen na vás, která varianta se vám více líbí.

Pokud jste animace v JavaScriptu nikdy nedělali a nejste v tom zběhlí, tak se vám možná budou na úvod hodit jednoduché tipy pro jejich tvorbu. Jestliže by se měl progressbar animovat, tak se bude hodnota většinu času pohybovat někde uvnitř. Nejprve to bude 0, pak 1, 2, 3 a nakonec to bude třeba 75 uložených v attributu data-procent (ten který jsme ověřovali selektorem). K zařazení této pohybující se proměnné do aplikace máte opět dvě varianty. První varianta je ta, že to budete celou dobu animace předávat v parametru, ta druhá, že si vytvoříte pomocnou proměnnou (osobně je pro přehlednost označuji tím, že začínají podtržítkem). Druhá mi osobně přijde o trochu lepší, protože dělali byste animaci, která může přibývat nebo ubývat (možná si říkáte na co ubývání, to proto, že jakmile implementuje obsluhu události pro změnu parametru, tak uživatel může zadat hodnotu menší než je dosavadní), tak musíte použít buď inkrementaci, nebo dekrementaci, ačkoliv to jde udělat všechno i v parametru přijde mi hezčí varianta pracovat s vlastností.

DOMsubtreeModified

Abychom mohli nějak za běhu kontrolovat změny parametrů, tak máme (jako už asi již tradičně) dvě možnosti. Ta první je, že budeme v určitém časovém intervalu kontrolovat každou hodnotu anebo si odchytíme událost DOMsubtreeModified. Ta se vyvolá vždy v okamžiku, když se cokoliv změní v stromu DOM. Ačkoli by tuto událost měli správně zpracovat všechny prohlížeče, zjistil jsem, že v Google Chrome nefunguje. Neustálá kontrola atributů je jednak asi mírně složitější, kód je méně přehlednější, jelikož tam je kolem toho spousta pomocného kódu a jak již jsem zmiňoval v odstavci o animacích, pro mobilní zařízení to není to pravé ořechové. Proto si odchyťte DOMsubtreeModified a obslužte si to podle libosti.

Možná vylepšení

Tímto byste měli mít progreessbary hotové a můžete se pustit do implementace dalších vylepšení, dbejte vždy na dobrý návrh, přemýšlejte a věřte, že přepisovat kód na pětkrát je zcela normální, někdy si to ani neuvědomujete. Nakonec vám vypíši, co byste mohli doimplementovat.

  • Volitelné animace, dejte možnost i neanimované verze, můžete použít attrbtu data-*
  • Možnost nastavování různých barev pro pozadí i samotný progress
  • Umožnit nastavení startovního úhlu, u některých aplikací se hodí víc když pregress začíná třeba v 3/5.
  • Podpora různé výšky šířky. Můžete třeba vycentrovat obsah, k tomu se dobře hodí možnost posunutí kontextu u plátna.

Doufám že se vám tento článek líbil, něco jste se přiučili, ale hlavně doufám že byl pro vás přínosný a vaše nadcházející výsledky v JavaScriptu budou zase o trochu lepší.

Doporučuji se zkoušet zapojovat do zdejších minisoutěží Machr na JavaScript. Trénujete nejen JavaScrit, ale i schopnost řešit určitou úlohu rychle a celý projekt stihnout dokončit do deadline. V Machru na JS se nehodnotí jenom výsledek, ale i zdrojový kód. A právě tento článek vás měl navést správnou cestou a dát vám některé tipy k správnému návrhu a psaní hezkého a dobře čitelného kódu. Právě kulatý progressbar je úloha jednoho předchozího machra. :)


 

Stáhnout

Staženo 408x (2.36 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

  Aktivity (1)

Článek pro vás napsal Michal Žůrek (misaz)
Avatar
Autor se věnuje tvorbě aplikací pro počítače, mobilní telefony, mikroprocesory a tvorbě webových stránek a webových aplikací. Nejraději programuje ve Visual Basicu a TypeScript. Ovládá HTML, CSS, JavaScript, TypeScript, C# a Visual Basic.

Jak se ti líbí článek?
Celkem (5 hlasů) :
3.43.43.4 3.43.4


 



 

 

Komentáře
Zobrazit starší komentáře (9)

Avatar
1Pupik1989
Člen
Avatar
1Pupik1989:

Nepřipadá mi, že by se tím kód nějak znepřehlednil. Jediné co se změní, že bez volání funkce ubude jedna operace, čili to 100% bude rychlejší. Samozřejmě úplně nejlepší řešení je používat vždy radiány a ne stupně. Do budoucna to ušetří spoustu práce a kódu. Podle mě ale u toho kódu to bude nejspíš jedno, protože při každém zavolání funkci vytvoří 4 další funkce. Lepší by se to řešilo v OOP.

new RadialProgressBar(elements[i])

To by chtělo ještě opravit na:

RadialProgressBar(elements[i])

Doufám že jsem nezněl jako blb. To jsou jen postřehy, nic už nikdy nikomu vyčítat nebudu, ať si každý píše jak chce. :D

 
Odpovědět 10.3.2014 16:36
Avatar
1Pupik1989
Člen
Avatar
1Pupik1989:

Promiň, ale když koukám na kód, tak si jim ani svým zápisem na přehlednosti moc nepomohl. Tohle je příklad, kde by šla oddělit logika od vykreslení a přitom opravdu použít prototype. Rozdělil bych to na metody jako jsem měl v objektu v soutěži já. "drawBackground", "drawProgress" a zde přidat i "drawText". Tohle je vlastně takové půl closure. Sice to má vnořené funkce, nicméně to není objekt a nic to nevrací. Nechci nějak radit, ale tohle bych se opravdu odnaučil. V pár případech je to dobrá věc, ale co se týče objektového psaní je to zlo. Jak jsem psal, operátor "new" ztrácí smysl. Vytvoří zbytečně objekt, který nic nepoužívá, čili to zabere paměť úplně k ničemu. Přesně takto jsem začínal a trvalo mi, než jsem logiku javascriptu pochopil a dozvěděl se, co se mu nejvíc líbí. Opravdu je prototype rychlejší a hlavně přehlednější.

Jen hrubý návrh, nějak jsem se tím nezabýval (15 min než jsem šel na pivo). Určitě by v closure nemusel být celý objekt, ale jen to co využívá vedlejší funkce. Ty by se ale daly napsat přímo do metod objektu.

http://www.itnetwork.cz/dev-lighter/318

 
Odpovědět 11.3.2014 23:49
Avatar
Odpovídá na 1Pupik1989
Michal Žůrek (misaz):

to je schválně, hned na začátek je že ti co to nechocou psát celé sami si mohou stáhnout zdrojáky a upravit je aby to bylo lepší, proto to není prototypované atd. Za deg2rad si stojím, nějaké násobení s pioverbuhvico mě nepřesvědčí, že je přehlednější, nehledě na to že si pořád musím pamatovat jak přesně funguje ten vzorec. Mám to tím číslem dělit? násobit? mocnit? odmocnit?

new Neco() volám když vytvářím instanci a Neco() když volám funkci, takhle to rozlišuje. Však každý si to přece může dělat po svém.

Editováno 12.3.2014 6:24
Odpovědět 12.3.2014 6:22
Nesnáším {}, proto se jim vyhýbám.
Avatar
1Pupik1989
Člen
Avatar
1Pupik1989:

V tomto případě ale vytvoříš instanci a nijak jí nevyužiješ. Vlastně je to funkce, která obsahuje lokální proměnné a funkce. K tomu není třeba instance, ale postačí funkci pustit s parametrem.

V matematice je potřeba si pamatovat hodně vzorců. Výběr funkce nebo proměnné už je na jedinci. Já mám všechny proměnné tak, abych násobil. Mám zafixováno, že násobení je rychlejší než dělení. Teď už to asi pravda moc nebude.

deg2rad jsem měl také jako funkci, ale časem si toho pamatuji víc a při honění rychlosti ji nemělo cenu používat. Vůbec když jsem přestal používat stupně úplně.

 
Odpovědět 12.3.2014 11:41
Avatar
Michal Žůrek (misaz):

Chrome 24 a DOMSUbtreeModified jim stále nic neříká

Odpovědět 11.4.2014 19:02
Nesnáším {}, proto se jim vyhýbám.
Avatar
1Pupik1989
Člen
Avatar
1Pupik1989:

DOMSubtreeModified jde celkem dobře simulovat bez časování. Stačí u všech DOM elementům upravit metody na manipulaci.

 
Odpovědět 11.4.2014 21:13
Avatar
Odpovídá na 1Pupik1989
Michal Žůrek (misaz):

jasně, ale to už není tak hezké.

Odpovědět 11.4.2014 21:16
Nesnáším {}, proto se jim vyhýbám.
Avatar
1Pupik1989
Člen
Avatar
1Pupik1989:

Nicméně funkční. Třeba Opera zrovna tu metodu také moc nepodporuje.

Editováno 11.4.2014 21:29
 
Odpovědět 11.4.2014 21:28
Avatar
Odpovídá na 1Pupik1989
Michal Žůrek (misaz):

to není metoda, je to událost.

Odpovědět 15.4.2014 8:48
Nesnáším {}, proto se jim vyhýbám.
Avatar
1Pupik1989
Člen
Avatar
1Pupik1989:

To nic nemění na tom, že to simulovat jde, když to tak nutně potřebuješ. Nicméně jsem moc nepochopil význam té události.

 
Odpovědět 15.4.2014 12:32
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 19. Zobrazit vše