HTML

Gyakorlat: képmegjelenítő plugin (prettyPhoto)

Ez most már egy igazán testhezálló feladat lesz. Egy képek stílusos megjelenítéséhez használható plugint fogunk készíteni, ami hasonló lesz, mint a már jól ismert "prettyPhoto" (persze annak azért egy egyszerűbb változata). Adott az oldalunkon egy kis galéria, formázottan, linkelt képekkel megpakolva. A struktúra ilyen formában épül fel:

<div id="gallery">
    <a href="kep1.jpg" title="Cím 1" target="_blank">
        <img src="kep1.jpg"/>
    </a>
    <a href="kep2.jpg" title="Cím 2" target="_blank">
        <img src="kep2.jpg"/>
    </a>
    <!-- és így tovább... -->
</div>

Egy "div' elemen belül sorakoznak a linkek, és azokon belül vannak a képek. Az "a" linkek tulajdonságai a "href", ami ugye a megjelenítendő képre hivatkozik, a "title" egy opcionális lehetősége, ha valamilyen cím-szöveget akarunk a képhez párosítani, és a "target"-hez beírt "_blank" érték biztosítana minket arról, hogy a hivatkozott kép egy új lapon nyíljon meg. Tehát működés szempontjából ez így önmagában szkript nélkül is megállja a helyét, viszont nekünk nem az a célunk, hogy egy új lapon megnyíljon a kép egy snassz fehér háttér előtt, hanem az aktuális lapon maradva egy látványos hatás kíséretében jelenítse meg azt. Az "img" szerepe pedig csak az, hogy a galériánkon belül is lássunk kicsinyített ízelítőket a képekből, melyekre kattintva előbukkan a nagyobb méret (éles helyzetben az "img"-be javasolt bélyegképeket elhelyezni a gyorsabb működés érdekében, az eredeti méretű kép eléréséhez úgyis csak az "a" tagokban szereplő elérhetőség játszik szerepet.

Na, adjunk akkor ennek egy pofás kis megjelenítést. Az én saját kreációmban ennek a pluginnak a neve "myPhoto" lesz. Tehát ennek definiálása a "gallery" nevezetű elememre nézve:

$('#gallery').myPhoto({maxh:450});

Igen, jól látjátok, tervezek ebbe is opciókat :) ebből egyik a "maxh" lesz, ami a megjelenítendő kép maximális magasságát fogja jelenteni (tehát a felbukkanó kép ennél nem lehet nagyobb - alapértelmezetten lehet 500). Egy második opciót is tervezek "labels" néven, aminek az értéke "true" vagy "false" (igaz-hamis) lehet, azt meghatározva, hogy szeretnénk-e a linkek "title" tulajdonságában lévő szöveget megjeleníteni a felbukkanó kép alatt. Akkor ezek alapján már pik-pak össze kell tudni dobni a plugin vázát...

(function($){
    $.fn.myPhoto = function(ops) {
        var defOps = {maxh:500,labels:true};
        ops = $.extend({},defOps,ops);
    };
})(jQuery);

Itt már nem szabad olyan dolognak szerepelnie, amit részletezni kellene :) úgyhogy azonnal ugrok is tovább, mivel most ismét egy újdonság következik...

Az .append() függvény:

Ahhoz hogy a plugin működjön, szükség lesz ugye egy újabb "img"-re, amibe az aktuálisan kiválasztott képet betöltjük, valamint - ha már úgyis ez a trendi - egy áttetsző fekete boxra, ami kitölti a képernyőt és a kép ez előtt fog felbukkanni. Ilyen elemek nem szerelnek alapesetben az oldalon és a készítőtől sem várhatjuk el, hogy hozza létre ezeket, mert különben a plugin nem fog működni... egy profi jQuery-függvénynek ezt meg kell tudnia oldani magától. A most említett problémát orvosolni tudja az "append" függvény... egy kiválasztott objektumon belülre beilleszthetünk egy újabb szöveget, vagy HTML kifejezést. Ha én azt akarom, hogy az oldalam "body" törzsén belül létrejöjjön egy új "div" elem, akkor egy "append"-del egyszerűen csak beírom:

$('body').append('<div id="new"></div>');

Ha nem akarok spórolni a hellyel, akár a "style" tulajdonságokat is egy csapásra letudhatom ezen belül. Ezután pedig már ugyanúgy tudok jQuery-vel hivatkozni a "new" elemre, mint bármelyik másikra. 

Ebben a gyakorlatban most azért kicsit hosszabb lesz ez, mint a példában volt, mert azért több dolgot is hozzáadok az oldalamhoz, stílusokat is adok hozzá, valamint utána majd még "css" függvénnyel kicsit tovább machinálom őket...

$('body').append('<div id="myPhoto_back" style="position:fixed;left:0px;top:0px;width:100%;height:100%;">
</div>')

.append('<table id="myPhoto_cont" style="position:fixed;left:0px;top:0px;width:100%;height:100%;">
<tr><td align="center" valign="top"><img/><br/><span></span></td></tr></table>');


$('#myPhoto_back').css('background','#000000').css('opacity','0.9')
.css('display','none');

$('#myPhoto_cont').css('display','none').find('span')
.css('font-family','Verdana,Arial')

.css('color','#ffffff').css('font-size','12px');
$('#myPhoto_cont img').css('border','20px solid #ffffff')
.css('box-shadow','0px 0px 20px #000000')

.css('margin-top','30px').css('max-height',ops.maxh+'px')
.css('margin-bottom','5px');

Ha sikerült megemészteni ezt a zsúfolt információhalmazt, akkor nézzük meg, mi is történek ezeknek a végrehajtása során... Itt még nem a kijelölt objektumot dolgoztatjuk, hanem a dokumentumunk törzsének, a "body"-nak a végére akarunk új elemeket létrehozni, ezért az "append" függvényt a "body"-ra hivatkozva alkalmazzuk. Alkotunk egy "myPhoto_back" nevezetű elemet, ami fix pozícióval a felső sarokba igazítva kitölti a teljes képernyőt, majd ugyanilyen tulajdonságokkal beillesztek egy táblát, melynek "td" elemén belül lesz középre igazítva (függőlegesen fentre) az "img", amibe majd a kijelölt képet töltjük be, az alatt pedig egy "span" fogja tartalmazni a képhez tartozó szöveget. További CSS-buherálásokkal a "myPhoto_back" hátterét feketére színezzük, 0.9-es átlátszóságot adunk neki, és elrejtjük, hogy kezdetben ne látszódjon. A "myPhoto_cont" táblát is láthatatlanná tesszük, illetve az azon belüli "span" elem szövegét formázzuk még: legyen Verdana (vagy Arial) betűtípus, fehér színnel, és 12 pixeles betűmérettel. Végül még a "myPhoto_cont" táblán belüli "img"-t pofozzuk ki kicsit: legyen egy 20 pixel nagyságú fehér szegélye, vessen egy 20 pixeles nagyságú fekete árnyékot (eltolás nélkül), beállítunk egy 30 pixeles margót a tetejére, hogy azért legyen egy kis távolság, megadjuk a maximális magasságának értékét az opciókban megadottnak megfelelően és végül még az aljára is teszünk egy 5 pixeles margót, hogy ne érjen közvetlenül hozzá a szöveg (már amennyiben az látszódni fog). 

Maga a szerkezet ezzel már felépült, már csak az események írása van hátra. Előtte viszont még szükség lesz bizonyos változók deklarálására itt a fő-függvényben...

var noclose=false;
var tmp_href;
var selected_a;

A legelső még egyenlőre nem releváns, a másik kettőt azért röviden kifejtem... a "tmp_href" változóban tároljuk majd el az aktuálisan kiválasztott kép elérhetőségét, majd ki fog derülni, hogy miért szükséges. A "selected_a" pedig magát a kiválasztott "a" elemet tárolja majd el :) ezt is meg fogjuk tudni, hogy minek.

Lényegében két fő eseményünk lesz (plusz egy mellékes): amikor a kép-linkre kattintva a plugin elemei megjelennek és betöltődik a hivatkozott kép, illetve amikor az áttetsző fekete részre kattintva ezek ismét eltűnnek (és még ehhez fog tartozni az a plusz is).

A megjelenítés:

Gondoljuk át nagy vonalakban, hogy miként is tudna működni ez a dolog... Rákattintunk a képre hivatkozó "a" linkre... kiolvassuk a link "href" attribútumának értékét, majd a "myPhoto_cont" elemen belüli "img"-nek az "src" tulajdonságát beállítjuk erre az értékre. Aztán már meg is jeleníthetjük a "myPhoto_back" és "myPhoto_cont" objektumainkat. Ez így működik is... megjelenik a fekete háttér és a betöltött kép is... de az "a" linkek viselkedése nem változik meg, ugyanúgy megnyitják a hivatkozást... esetünkben egy fokkal jobb, egy új lapon... de lássuk be, ez is idegesítő :) Tehát a "href" tulajdonságát a megnyitás idejére módosítanunk kell, hogy ne történjen elnavigálás, illetve ha bezárjuk ezt a kis nézegetőt, vissza kell, hogy adjuk ezt a "href" értéket a gazdájának (a szóban forgó "a" linknek), és ehhez tudnunk kell, hogy melyik link objektum volt az, akit megnyitottunk (ehhez kell a "selected_a" változó), és hogy milyen címre hivatkozott eredetileg (ezt pedig a "tmp_href" tárolja majd). Akkor ezek alapján:

$(this).find('a').click(function(){
    selected_a=$(this);
    tmp_href=selected_a.attr('href');
    selected_a.attr('href','javascript:');
    $('#myPhoto_cont img').attr('src',tmp_href);

    $('#myPhoto_back').fadeIn(250);
    $('#myPhoto_cont').fadeIn(500);
});

Szóval... egy kattintás esemény érkezik a galériánkon belüli "a" linkekre... a függvényen belül a "this" természetesen már az adott "a" elemet jelenti. Nem is szarozunk, már az elején a "selected_a" változóba beletesszük az aktuális elemet és a továbbiakban ezzel is dolgozunk tovább... a "tmp_href"-be pedig bekerül ennek a kiválasztott linknek a képre mutató hivatkozása. Most pedig távolítsuk el ezt a hivatkozást a linkből, változtassuk meg az elem "href" tulajdonságát, én a "javascript:" kifejezést találtam célszerűnek, mert olyankor a link nem navigál sehova, hanem szkriptet hajtana végre, tehát most nem fog csinálni semmit. Akkor a "myPhoto_cont" tárolónkban elhelyezkedő "img" elemünknek az "src" tulajdonságában betesszük szépen a hivatkozást, amit a "tmp_href" változóban őrzünk és szépen el is kezdi betölteni a képet. Ekkor a jó öreg "fadeIn" effektekkel elővarázsoljuk az áttetsző fekete hátteret és a képet magában foglaló táblát is.

A .text() függvény

Ezt most azért vesszük elő, mert használni fogjuk :) A segítségével egy megadott elem belsejében lévő tartalmat tudunk megváltoztatni. Az "append"-től eltérően nem hozzáfűzi a megadott szöveget, hanem egy az egyben felülírja. Például, ha van egy ilyen rész a HTML kódunkban:

<div id="box">
    <p>Paragrafus</p>
    <img src="pic.jpg"/>
</div>

Akkor ezzel a függvénnyel gyökeresen megváltoztathatjuk ezt a felépítést :) Azaz... miután lefuttatunk egy $('#box').text('Ez az új tartalom'); szkriptet, attól kezdve a fenti szerkezet a böngésző számára ilyen formában létezik tovább:

<div id="box">
    Ez az új tartalom
</div>

Ezt kihasználva érjük el, hogy a "span" elemünk jelenleg üres belsejébe beírjuk az aktuális link "title" tulajdonságába helyezett szöveget. Persze azt is figyelembe kell venni, hogy ezt feltételhez kötöttük: ha a "label" opció értéke igaz. Akkor a fenti "click" eseményben a függvényt még kiegészítjük... mielőtt "fadeIn" hatásokkal megjelenítjük a plugin elemeit, írjuk be ezt a sort:

if(ops.labels)
    $('#myPhoto_cont span').text(selected_a.attr('title'));

Ha az opciókban szereplő "labels" változó értéke igaz, akkor a kiválasztott "a" elem attribútumai közül a "title" értékét kikapva, azt egy "text" függvénnyel írjuk be a "myPhoto_cont"-ban lévő "span" elem belső tartalmaként. 

A megjelenítő bezárása:

Szemügyre vettük a képet, boldogok vagyunk... már nagyban is láthattuk, amit eddig csak kicsiben... most akkor  be kellene zárni, hogy tovább böngészgessünk. Ehhez csak annyit kellene tennünk, hogy kattintsunk valahova félre egyet és máris minden szépen eltűnik. Ez akkor egy kattintás-eseményt jelent, méghozzá a "myPhoto_cont" elemre vonatkozóan, mivel ez van legfelül, ami mindent eltakar és beteríti a teljes képernyőt.

$('#myPhoto_cont').click(function(){
    selected_a.attr('href',tmp_href);
    $('#myPhoto_cont span').text('');

    $('#myPhoto_back').fadeOut(150);
    $('#myPhoto_cont').fadeOut(150);
});

Ezekre volt csak szükség... ha minden igaz, a "selected_a" változónk még mindig töretlenül azt az "a" elemet tartalmazza, amire utoljára klikkeltünk, a "tmp_href" pedig annak eredeti hivatkozását (mert ugye most perpill a "javascript:" kifejezés lapul a "href" tulajdonságában, ha így hagynánk, nem tudnánk már újra megnyitni). Akkor most szépen annak az elemnek a "href" attribútumába visszatesszük az eredeti hivatkozást. Még a teljesség kedvéért kitöröljük a "span"-ből is a feliratot... Most már minden a helyén van, két "fadeOut" effekttel eltüntetjük a kép tárolóját és a borító elemet. 

Már szinte készen is vagyunk, de akad még egy apró probléma... ha a "myPhoto_cont" területére kattintunk (ami jelenleg az egész képernyőt lefedi), a bezárás akkor is végrehajtódik, ha a megjelent képre kattintunk rá... vagy ha lennének PL lapozást végrehajtó gombok, akkor is bezárulna az egész, pedig nem kellene... mi azt akarjuk, hogy csak akkor zárja be, ha a kép területén kívül kattintunk valahova. Na, ehhez kell még egy plusz függvény és a "noclose" változó...

$('#myPhoto_cont img').click(function(){noclose=true;});

A "noclose" értéke szintén "true", vagy "false" lehet. Ha a "noclose" igaz, az azt jelenti, hogy tilos bezárni a megjelenítőt, csak hamis érték esetén engedélyezett. Szóval... az oké, hogy a "myPhoto_cont"-ra kattintva megindul a bezárás, de még ehhez társul egy másik esemény: ha a "myPhoto_cont"-on belüli "img"-re történik a kattintás, akkor állítsuk a "noclose" értékét igazra. Most már csak ennek figyelembevételével kell kiegészíteni a "myPhoto_cont" eseményét...

$('#myPhoto_cont').click(function(){
    if(!noclose){
        selected_a.attr('href',tmp_href);
        $('#myPhoto_cont span').text('');
        $('#myPhoto_back').fadeOut(150);
        $('#myPhoto_cont').fadeOut(150);
    }else noclose=false;
});

Történt egy kattintás a "myPhoto_cont" elemen. Ha a "noclose" értéke nem igaz, akkor ez a kép területén kívül történt (mert ha azon belül, akkor az értékét igazra állította volna), tehát végrehajtódik a már fent is kifejtett bezárási procedúra. Ellenkező esetben viszont nem bántunk semmit, minden marad látható, a "noclose" értékét pedig újra visszaállítjuk hamisra, hogy majd a következő "click" eseménynél is megfelelően tudjunk vele ellenőrizni.

Ez már ugye azért nem volt egy kispályás meló? :) Ezt a részt sem hagyhatom itt üres kézzel, mellékelem a saját megoldásomat is. Ha valaki le akarja tölteni, itt éri el: KLIKK

Szólj hozzá: