| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
╚o vlastne chceme?
V prvom rade sa musφme zamyslie¥ nad t²m, Φo vlastne chceme:
- Chceme urobi¥ jednoduch² newsticker, v ktorom sa bude pohybova¥ nieko╛ko textov²ch sprßv sprava do╛ava.
- Chceme aby jeden dokument mohol obsahova¥ neobmedzenΘ mno₧stvo newstickerov.
- Ke∩ sa textovß sprßva dostane na koniec newstickeru, mala by sa na chvφ╛u zastavi¥ a a₧ potom odφs¥.
- Chceme ma¥ mo₧nos¥ ovplyvni¥ r²chlos¥ pohybu textu a dσ₧ku pauzy.
- Skript by mal fungova¥ v prehliadaΦoch podporuj·cich Ütandardy, priΦom informßcie v ≥om zobrazovanΘ by mali by¥ prφstupnΘ vo vÜetk²ch prehliadaΦoch.
- Forma by mala by¥ oddelenß od obsahu. Skript by mal by¥ Φo najviac oddelen² od dokumentu.
Je dos¥ d⌠le₧itΘ, aby sme sa pred pφsanφm skriptu v₧dy zamysleli nad t²m, Φo od neho oΦakßvame. V∩aka tomu budeme ma¥ u₧ vopred dobr· predstavu o tom, Φo budeme potrebova¥ a ako budeme postupova¥. Pokia╛ vßs v²sledok nßÜho sna₧enia zaujφma u₧ teraz, pozrite si mal· ukß₧ku.
Ako to urobφme?
Budeme vychßdza¥ zo zoznamu s odrß₧kami. Je jednoduch², zobrazφ sa korektne vo vÜetk²ch prehliadaΦoch a presne vystihuje Ütrukt·ru, ktor· budeme potrebova¥ û element UL reprezentuje dr₧iak (rßmΦek), v ktorom sa pohybuj· jednotlivΘ sprßvy (element LI):
<li>prva sprava</li>
<li>druha sprava</li>
<li>tretia sprava</li>
</ul>
Zoznam naformßtujeme pomocou CSS. Aby sme zbytoΦne nerozhodili vzh╛ad strßnky v archaick²ch prehliadaΦoch (v ktor²ch nßÜ skript nebude fungova¥), ulo₧φme definφcie CSS do samostatnΘho s·boru ("newsticker.css") a ten prilinkujeme pomocou met≤dy @import:
Samotn² skript tie₧ ulo₧φme do samostatnΘho s·boru ("newsticker.js") a k dokumentu ho pripojφme Ütandardn²m sp⌠sobom:
Postup bude celkom jednoduch². Najprv si pomocou CSS naformßtujeme zoznam. JednotlivΘ sprßvy umiestnime do dr₧iaku absol·tne a animova¥ ich budeme pomocou zmeny CSS atrib·tu left v pravideln²ch intervaloch. Pre lepÜiu predstavu o tom, ako to funguje, je pripravenß ilustrovanß verzia skriptu, kde to vÜetko pekne vidno.
Te≤ria absol·tnej relativity
Element UL si oznaΦφme triedou newsticker (<ul class="newsTicker">). Ke∩₧e chceme ma¥ mo₧nos¥ umiestni¥ do jednΘho dokumentu viac newstickerov, je vhodnejÜie pou₧i¥ triedu (class) namiesto unikßtneho ID. Zadefinujeme si zßkladn² vzh╛ad dr₧iaku. Dajme tomu, ₧e bude rßmovan² jednopixelovou Φiarou. To je Φisto kozmetickß zßle₧itos¥. ╧alej by mal ma¥ v²Üku asi tak 1,5 em, aby sa nßm tam v pohode voÜiel jednoriadkov² text. ╚o je vÜak d⌠le₧itΘ, je schovanie preteΦenΘho obsahu (overflow). Chceme toti₧, aby text, ktor² prßve nie je v dr₧iaku, nebolo vidno (v opaΦnom prφpade by nßm tie texty poletovali hore-dole po strßnke a newsticker by nemal ve╛mi zmysel). A ke∩₧e mßme naformßtovan² odrß₧kov² zoznam (kv⌠li starÜφm prehliadaΦom), ale chceme ho zobrazova¥ ako normßlne texty, zruÜφme odrß₧ky a r⌠zne defaultnΘ odsadenia (padding, margin). NaÜa definφcia teda bude vyzera¥ asi takto:
border: 1px solid #000;
height: 1.5em;
list-style: none;
padding: 0;
margin: 0;
overflow: hidden;
}
Ch²ba nßm tam vÜak eÜte jedna ve╛mi d⌠le₧itß vec. Aby sme mohli jednotlivΘ sprßvy pohodlne animova¥, potrebujeme ich umiestni¥ absol·tne vzh╛adom k dr₧iaku. Nie je to niΦ komplikovanΘ, ale ve╛a ╛udφ mß o absol·tnom pozicovanφ mylnΘ predstavy. Myslia si toti₧, ₧e absol·tna pozφcia elementu sa vz¥ahuje k ╛avΘmu hornΘmu rohu dokumentu. Nie je to pravda a Üpecifikßcia CSS2 nßm to potvrdφ. V modele absol·tneho pozicovania je blok pozicovan² vzh╛adom k rodiΦovskΘmu bloku.
V praxi to vÜak vyzerß tak, ₧e pokia╛ rodiΦovsk² blok nemß zadefinovan² typ pozicovania (t.j. relative alebo absolute), neovplyv≥uje polohu absol·tne pozicovanΘho bloku. Tak₧e pokia╛ nßÜmu dr₧iaku pridßme eÜte definφciu position: relative;, sme hotovφ. NßÜ newsticker bude normßlne umiestnen² v toku dokumentu a jednotlivΘ sprßvy v ≥om bud· napozicovanΘ sprßvne.
Vzh╛ad sprßv
Naformßtovanie jednotliv²ch sprßv bude jednoduchΘ. U₧ sme si povedali, ₧e bud· pozicovanΘ absol·tne. ╧alej im zruÜφme (podobne ako u dr₧iaku) okraje a odsadenia vypl²vaj·ce z toho, ₧e p⌠vodne obsahovali odrß₧ky. Nakoniec eÜte vypneme zalamovanie textu (vytvßralo by to ve╛mi ÜkaredΘ efekty):
padding: 0;
margin: 0;
position: absolute;
white-space: nowrap;
}
ZaΦφname skriptova¥
Newsticker mßme naformßtovan². Ke∩ sa na v²sledok nßÜho sna₧enia pozrieme teraz, uvidφme, ₧e v rßmΦeku s· jednotlivΘ sprßvy naukladanΘ jedna na druh· a Φakaj· len na to, aby s nimi niekto zaΦal h²ba¥. Vpred!
Vytvorφme si prßzdny objekt newsTicker. Ten bude fungova¥ ako akΘsi puzdro pre vÜetky premennΘ a funkcie, ktorΘ budeme pou₧φva¥. Pokia╛ vytvßrame nejak² komplexnejÜφ skript, je lepÜie ho zapuzdri¥ do jednΘho objektu. Spreh╛adnφme si t²m prßcu a predφdeme zbytoΦn²m konfliktom pomenovania premenn²ch a podobne. ObzvlßÜ¥ sa to vyplatφ, ak pφÜeme skripty pre projekt, kde bude ved╛a seba tak²chto skriptov viac. Tak₧e si vytvorφme ten objekt:
Teraz sa zamyslφme nad t²m, akΘ funkcie a objekty vlastne budeme potrebova¥ a vytvorφme si ich. Nesk⌠r pravdepodobne budeme musie¥ prida¥ nejakΘ pomocnΘ veci, ale aspo≥ uvidφme zßkladn· kostru skriptu a budeme sa jej m⌠c¥ dr₧a¥.
- Asi budeme potrebova¥ nejak· funkciu, ktorß nßÜ newsTicker zinicializuje a predß mu zßkladnΘ atrib·ty ako r²chlos¥ posunu, odmlku a podobne. Nazveme ju init().
- Ve╛mi d⌠le₧itß bude funkcia, ktorß bude vykonßva¥ samotn² pohyb sprßv. Bude sa vola¥ roll().
- Vytvorφme si eÜte objekt, v ktorom budeme skladova¥ informßcie, ktorΘ budeme potrebova¥. Nazveme ho ticker a bude to pole. Ka₧d² newsticker, ktor² bude na strßnke, si bude uklada¥ svoje informßcie v podobe ∩alÜieho po╛a do po╛a ticker. Mßte v tom gulßÜ? To niΦ. Predstavte si to ako dvojrozmernΘ pole. Alebo eÜte jednoduchÜie ako tabu╛ku. Ka₧d² riadok reprezentuje jeden newsticker, stσpce obsahuj· informßcie. V praxi to bude vyzera¥ asi takto:
id element r²chlos¥ krok odmlka aktußlna sprßva 0 element1 10ms 2px 1000ms 3 1 element2 24ms 1px 0ms 1
Pozrime sa teda na to, ako zatia╛ vyzerß obsah nßÜho s·boru newsticker.js:
newsTicker.ticker = new Array();
newsTicker.init = function() {};
newsTicker.roll = function() {};
Inicializßcia
Pri inicializßcii newstickera nßs bude zaujφma¥:
- Ktor² element mßme prerobi¥ na newsticker? (Mal by to by¥ element UL.)
- Ak² ve╛k² skok urobia sprßvy pri ka₧dom posune?
- Ako r²chlo sa bud· jednotlivΘ sprßvy pos·va¥ (t.j. v ak²ch Φasov²ch intervaloch)?
- Ako dlho ostan· stߥ, ke∩ sa dostan· na zaΦiatok newstickeru?
V skutoΦnosti je pre nßs d⌠le₧it² jedin² ·daj - element, ktor² budeme formßtova¥. VÜetky ostatnΘ ·daje m⌠₧eme doplni¥ nejak²mi defaultn²mi hodnotami. Poskytova¥ v tak²chto skriptoch defaultnΘ hodnoty je celkom dobrß vec. Na jednej strane dßvame u₧φvate╛ovi mo₧nos¥ nastavi¥ si vÜetko tak ako chce a potrebuje. Na druhej strane mu vÜak dßvame mo₧nos¥ zjednoduÜi¥ si prßcu. Okrem toho, pokia╛ v nejakom projekte vÜade pou₧φvame newstickery s defaultn²mi hodnotami, iba na jednom mieste potrebujeme nieΦo mierne neÜtandardnΘ, bolo by zlΘ, keby sme kv⌠li tomu museli bu∩ pφsa¥ nov· funkciu, alebo uvßdza¥ tie istΘ defaultnΘ hodnoty pri ka₧dom volanφ skriptu.
Tak₧e m⌠₧eme zaΦa¥. Najprv si vy₧iadame atrib·ty, ktorΘ sa bud· predßva¥ funkcii init():
Prvß vec, ktor· skript urobφ, bude zbe₧nß kontrola vstupu a prostredia, aby sa zbytoΦne nesna₧il o nieΦo, Φo nie je mo₧nΘ, a nevyhadzoval chyby. Prekontrolujeme teda, Φi sme funkcii poslali atrib·t elm, Φi sa tento atrib·t odkazuje na element typu UL a Φi pou₧it² browser podporuje met≤dy, ktorΘ s· pre skript d⌠le₧itΘ (v naÜom prφpade je to met≤da document.getElementsByTagName). V prφpade, ₧e s· podmienky splnenΘ, funkcia urobφ Φo mß a vrßti nßm true. Ak nie, vrßti false a niΦ neurobφ:
if (elm && (elm.tagName == "UL") && document.getElementsByTagName) {
// obsah funkcie
return true;
}
return false;
}
Nßsledne si overφme, Φi s· zadanΘ ∩alÜie atrib·ty, a ak nie, nahradφme ich defaultn²mi hodnotami. Ke∩₧e vÜetky ostatnΘ atrib·ty s· Φφsla, bude ich kontrola jednoduchß:
if (isNaN(speed)) {var speed = 25};
if (isNaN(delay)) {var delay = 1000};
Toto vÜetko je sφce peknΘ, ale zatia╛ je to len vata. NßÜ skript a₧ doteraz neurobil niΦ konkrΘtne. ╚o vÜak od neho pri inicializßcii oΦakßvame? V podstate od neho chceme tri veci:
- Aby naÜiel vÜetky sprßvy, ktorΘ bud· v newstickeri rotova¥ a umiestnil ich tak, aby nßm nezavadzali.
- Aby ulo₧il potrebnΘ ·daje do premennej newsTicker.ticker.
- Aby nakoniec spustil samotn² pohyb sprßv.
Sprßvy nßjdeme jednoducho. Ide o vÜetky elementy LI v rßmci nßÜho elementu UL. Ich zoznam si ulo₧φme do doΦasnej premennej message:
Nßsledne si celΘ pole message prejdeme a ka₧d· sprßvu posunieme do╛ava o jej vlastn· Üφrku (t.j. prav² okraj sprßvy bude tam, kde je ╛av² okraj dr₧iaku). V∩aka absol·tnemu pozicovaniu proste odpoΦφtame Üφrku sprßvy od nuly:
message[i].style.left = 0 - message[i].offsetWidth + "px";
}
V podstate t²m simulujeme situßciu, kedy u₧ vÜetky sprßvy prebehli a cel² cyklus ich rotßcie mß zaΦa¥ odznova. PreΦo? To si povieme o chvφ╛u, ke∩ budeme rozobera¥ funkciu na pohyb sprßv. Teraz toti₧ eÜte musφme ulo₧i¥ do po╛a newsTicker.ticker potrebnΘ ·daje. Budeme potrebova¥ to, Φo sme u₧ funkcii predali ako atrib·ty, plus poradovΘ Φφslo sprßvy, ktorou prßve pohybujeme. Ke∩₧e pri inicializßcii simulujeme ukonΦen² cyklus, ulo₧φme ako poradovΘ Φφslo Φφslo poslednej sprßvy:
newsTicker.ticker[tickerID] = new Array(elm, step, speed, delay, message.length-1);
Premennß tickerID je unikßtny identifikßtor newstickeru v dokumente. Vedie¥ jeho ID je u₧itoΦnΘ, preto₧e nesk⌠r bude staΦi¥, ke∩ sa odkß₧eme na toto ID, a budeme ma¥ v∩aka po╛u newsTicker.ticker k dispozφcii vÜetky ·daje, ktorΘ budeme potrebova¥. A hne∩ to aj vyu₧ijeme, preto₧e zavolßme funkciu roll() s t²mto ID:
èkatule, h²bte sa
Funkcia roll() na svojom konci nastavφ timeout, v ktorom zavolß sama seba a tak plynulo animuje jednotlivΘ sprßvy. V podstate mß na starosti tri veci:
- Zistφ, Φi u₧ nßhodou sprßva neprebehla na koniec a ak ßno, zaΦne pos·va¥ ∩alÜiu.
- Rozhoduje, za ak² Φas sa sprßva opΣ¥ posunie.
- Fyzicky posunie sprßvu.
Najprv vÜak musφ zisti¥, Φi jej niΦ nebrßni v tom, aby to urobila. Ako atrib·t jej poÜleme ID newstickeru, aby vedela, Φφm mß vlastne h²ba¥. Toto ID musφ by¥ Φφslo a v poli newsTicker.ticker musφ existova¥ polo₧ka s dan²m ID:
if (!isNaN(id) && newsTicker.ticker[id]) {
// obsah funkcie
return true;
}
return false;
}
Vytvorφme si premenn· ticker, ktorß bude referenciou na polo₧ku z po╛a newsTicker.ticker s aktußlnym ID. Tßto premennß je v podstate zbytoΦnß a zaobiÜli by sme sa aj bez nej, ale zjednoduÜφ a zpreh╛adnφ nßm k≤d:
Pomocou tejto premennej sa teraz jednoducho a r²chlo m⌠₧eme dosta¥ k ulo₧en²m informßcißm, ktorΘ potrebujeme. Tak₧e naprφklad element, s ktor²m pracujeme, je ticker[0], poradovΘ Φφslo aktußlnej sprßvy je zas ticker[4]. Vytvorφme si ∩alÜie dve pomocnΘ premennΘ. Jedna z nich (message) bude obsahova¥ zoznam sprßv a druhß (actualMessage) bude odkazova¥ priamo na aktußlnu sprßvu:
var actualMessage = message[ticker[4]];
Ke∩ mßme pripravenΘ tieto pom⌠cky, m⌠₧eme sa zaΦa¥ rozhodova¥. Musφme si toti₧ zisti¥, Φi aktußlna sprßva nedorazila na koniec svojej cesty a nenastal Φas posla¥ ∩alÜiu. Ak ßno, musφme si zisti¥, Φi aktußlna sprßva nßhodou nebola poslednß v zozname a ak ßno, poÜleme opΣ¥ prv·. Nov· sprßvu si potom umiestnime za dr₧iak newstickeru, aby na nßs vykukla z pravej strany.
if (ticker[4]+1 == message.length) {
ticker[4] = 0;
} else {
ticker[4] = ticker[4] + 1;
}
actualMessage = message[ticker[4]];
}
Nov· sprßvu si vÜak nesmieme umiestni¥ len tak, na koniec dr₧iaku. Chceme toti₧, ke∩ sa dostane na nulov· pozφciu (t.j. na zaΦiatok dr₧iaku), aby sa na chvφ╛u zastavila (vi∩ premennß delay pri inicializßcii). Preto ju musφme umiestni¥ tak, aby jej pozφcia bola delite╛nß dσ₧kou kroku:
Spome≥me si eÜte, ako sme pri inicializßcii simulovali, ₧e u₧ vÜetky sprßvy prebehli. To prßve kv⌠li tomu, aby pri prvom spustenφ tejto funkcie boli splnenΘ vÜetky podmienky a prvß sprßva naozaj vyrazila ako prvß.
Po∩me teraz fyzicky posun·¥ aktußlnu sprßvu. O ko╛ko pixelov sa mß posun·¥, to nßm prezradφ premennß ticker[1]. Novß pozφcia sprßvy teda bude aktußlna pozφcia mφnus posun. Aktußlnu pozφciu zistφme dotazom na style.left aktußlnej sprßvy. To nßm vÜak vrßti pozφciu aj s mernou jednotkou (px), tak₧e si z nej musφme vyparsova¥ celΘ Φφslo:
Nakoniec sa eÜte musφme rozhodn·¥, za ako dlho opΣ¥ posunieme sprßvu ∩alej. Ak toti₧ dorazila na zaΦiatok dr₧iaku, chceme, aby sa na chvφ╛u zdr₧ala. Pomocou met≤dy setTimeout() si teda zavolßme funkciu newsTicker.roll s aktußlnym ID a pod╛a pozφcie aktußlnej sprßvy sa rozhodneme, Φi ju posunieme za normßlny Φas (speed), alebo si ju tam chvφ╛u podr₧φme (delay).
Koniec dobr², vÜetko dobrΘ
A to je vlastne vÜetko. To, Φo sme si predsavzali, sme splnili. Teraz m⌠₧eme skript bu∩ necha¥ tak, alebo sa s nφm ∩alej hra¥, rozÜirova¥ ho a zdokona╛ova¥. Samozrejme m⌠₧eme k≤d trochu zoptimalizova¥ a zjednoduÜi¥ (treba skrßten²mi zßpismi podmienok, ktorΘ som kv⌠li nßzornosti radÜej nepou₧φval). M⌠₧eme prida¥ novΘ funkcie, naprφklad aby sa pohyb newstickeru zastavil, ke∩ na≥ho ukß₧eme myÜou, a opΣ¥ spustil, ke∩ myÜ dßme preΦ. M⌠₧eme sa pok·si¥ ten skript dopφsa¥ tak, aby fungoval aj v Ütandardy nepodporuj·cich prehliadaΦoch.. Malou zmenou v k≤de to m⌠₧eme prepφsa¥ tak, aby sa text nepohyboval horizontßlne, ale vertikßlne. Alebo to m⌠₧eme necha¥ tak ako to je.
SlabΘ miesta
NiΦ nie je dokonalΘ a tento skript u₧ v⌠bec nie. NiektorΘ jeho problΘmy s· drobnosti, ktorΘ sa daj· ╛ahko vyrieÜi¥, nieΦo je zas komplikovanejÜie. Toto s· problΘmy, ktorΘ na tom skripte vidφm ja:
- Pri inicializßcii je niekedy mo₧nΘ vidie¥ vÜetky sprßvy nakopenΘ na seba. A₧ po tom, Φo sa pri inicializßcii posun· mimo dr₧iak, je vÜetko ako mß by¥. Tento problΘm sa dß vyrieÜi¥ ve╛mi jednoducho. V definφcii CSS staΦφ nastavi¥ polo₧kßm zoznamu display: none a pri inicializßcii im vrßti¥ display: block.
- Ve╛k²m problΘmom je Opera. Vlastne iba jej starÜie verzie. Tie sφce zvlßdaj· CSS a met≤du @import, ale zlyhßvaj· v podpore DOM. Tak₧e v Opere 5 skript v⌠bec nefunguje, preto₧e nie je podporovanß met≤da getElementsByTagName(). A v Opere 6 zas nefunguje orezßvanie obsahu elementu pomocou atrib·tu overflow, tak₧e jednotlivΘ sprßvy vidno, aj ke∩ vyjd· mimo dr₧iak. S· to urΦite neprφjemnΘ chyby a ich oprava by si vy₧iadala r⌠zne hacky, workaroundy a browser sniffing. V najnovÜej verzii Opery vÜak tento skript funguje sprßvne a ja si myslφm, ₧e Opera je minoritn² prehliadaΦ, ktorΘho u₧φvatelia vedia Φo robia, zaujφmaj· sa o svoj prehliadaΦ a ve╛mi r²chlo prechßdzaj· na novΘ verzie, tak₧e to pod╛a m≥a zas a₧ takß katastrofa nie je.
- Pou₧itie met≤dy offsetWidth nie je celkom ΦistΘ. Sprßvne by sa mala aktußlna Üφrka elementu zis¥ova¥ pomocou document.defaultView.getComputedStyle(mojElement, "").getPropertyValue("width"), ale bohu₧ia╛, zatia╛ najrozÜφrenejÜφ prehliadaΦ IE to nepodporuje. A tak som sa pri pφsanφ skriptu musel uch²li¥ k offsetWidth, Φo sφce nie je Ütandardn² sp⌠sob, ale zato je podporovan² vo vÜetk²ch momentßlne aktußlnych prehliadaΦoch. Mo₧no by stßlo za to napφsa¥ si na zis¥ovanie aktußlnych rozmerov elementu vlastn· funkciu (ale to u₧ je o nieΦom trochu inom).
PraktickΘ ukß₧ky
- zßkladnß verzia skriptu (verzia, ktor· sme spolu napφsali v tomto Φlßnku)
- rozÜφrenß verzia skriptu (doplnenß o niektorΘ doplnkovΘ funkcie, s· v nej tie₧ opravenΘ niektorΘ kozmetickΘ chyby)
- ilustrovanß verzia skriptu (pokia╛ nerozumiete presne, ako tento skript funguje, tßto verzia vam to nßzorne ukß₧e, ako pod rentgenom)
Bugreporting a vylepÜovanie
Som si ist², ₧e sa nßjdu r⌠zne drobnΘ chyby v Üpecifick²ch prehliadaΦoch na Üpecifick²ch platformßch. Budem rßd, ak ma o nich budete (s prφpadn²m nßvrhom ako ich opravi¥) informova¥ v komentßroch. Takisto rßd privφtam vÜetky nßvrhy a nßpady ako skript vylepÜi¥ alebo rozÜφri¥ o u₧itoΦnΘ funkcie.