Statikus oldal lapozása görgetéssel

A félreértések elkerülése végett, nem arról lesz szó, hogyan tölthetsz be új tartalmat görgetéssel, hanem egy statikus weblap egyes részei között válthatsz úgy, mintha azok önálló oldalak lennének.

Megjegyzem, nem biztos, hogy én vagyok a legalkalmasabb ember ennek a témának a kitárgyalására, de minden esetre egy, a mai böngészőkben működő megoldást részletesen bemutatok és a tőlem telhető módon általánosan is beszélek a témáról.

Akit csak a végeredmény érdekel mindenféle rizsa nélkül, kattintson a tartalomjegyzékben a "Csak a végső kód" linkre.

Tartalom

Variációk

Ilyen hatást többféle módon is el lehet képzelni. Mondok két példát is.

1. lehetőség: Lehetne, hogy az egérrel görgetés a gombnyomást helyettesítse, vagyis a görgetés hatására késleltetés nélkül betölt egy másik tartalom. Ekkor görgetősáv sem feltétlenül szükséges. Elképzelhető prezentációként.

2. lehetőség: Úgy is lehet, hogy görgetés közben az aktuális tartalom egy bizonyos pozicióig a képernyőn marad és csak az után vált. Ennek előnye, hogy közben más animáció is futhat az oldalon. Például a görgetősáv poziciójától függően az aktuális oldal egyre áttetszőbb lesz, míg az új oldal egyre inkább előtérbe kerül.

Hozzávalók

Görgetés figyelése: Tudni kell figyelni, ha görgettem az oldalom. Ehhez a "scroll" eseményre kell feliratkozni, amit a jQuery scroll metódusa böngészőfüggetlenül megold.

Pozició lekérdezése: Nyilván szükség van a görgetősáv (már, ha van ilyen) aktuális poziciójára. A jQuery scrollTop() metódusa használható erre.

Betöltendő terület kiválasztása: Az aktuális pozició alapján el kell tudni dönteni, melyik oldal jelenjen meg a képernyőn. Ezt persze csak akkor, ha van görgetősáv. Egyéb esetben egy segédváltozó használható arra, hogy épp melyik oldal aktív. Ez növelhető és csökkenthető görgetés hatására.

Teljes ablakra méretezés: Tudni kell az aktuális oldalt a böngészőablak teljes magasságában és szélességében megjeleníteni. Ehhez egy kis CSS kell.

Görgetősáv megjelenítése: Ez ugye céltól függ, de ha szükséges, ehhez a legegyszerűbb talán a "body" magasságának akkorára állítása, amennyi az összes oldal magasságának összege. Az viszont, hogy ez mekkora, függ a böngészőablak aktuális méretétől, ezért ehhez javascriptet fogok használni.

Megoldás

Megoldásként a második variációt készítem el.

Animáció nélkül

Először is berakom az oldalak tartalmát egy-egy div-be.

<div id="pages">
    <div class="page">Page 1</div>
    <div class="page">Page 2</div>
    <div class="page">Page 3</div>
    <div class="page">Page 4</div>
    <div class="page">Page 5</div>
</div>

Ezeknek a magassága a fent leírtak alapján a böngészőablak magassága kell legyen. Ezt az alábbi CSS megoldaná:

.page {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

De jelen esetben szeretnék görgetősávot, amihez a body magasságát meg kell növelnem. Akkor viszont az abszolút pozicióval az oldalaim is kimozognának a képből. Tehát a pozició legyen "fixed".

.page {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

Igen ám, de ha ezt így hagyom, az összes oldalam egymás tetején lesz. Tehát egyet mindig meg kell jeleníteni, a többit elrejteni. Ezért egy "active" osztály jelölheti, minek kell megjelenni, és azt kell a megfelelő div-re beállítani. Ezt persze megoldja majd a javascript, de mégis jobb, ha a javascript kikapcsolásával sem omlik össze a weblap, maximum csak az első oldal látszik. Ennek érdekében kiegészül a HTML-ben az első div az "active" osztállyal, a CSS pedig a láthatóságra vonatkozó szabályokkal:

.page {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    display: none;
}
.page.active {
    display: block;
}
<div class="page active">Page 1</div>
<div class="page">Page 2</div>
<div class="page">Page 3</div>
<div class="page">Page 4</div>
<div class="page">Page 5</div>

Nagyszerű, már csak a javascript hiányzik és némi csinosítás. De kezdem a lényeggel, a javascripttel. Be kell húzni a jQuery egyik verzióját, ami le is tölthető, de CDN-ről is használható. Tesztelés alatt nekem ez jó is lesz. A https://code.jquery.com/ oldalon a 3.1.1-es verzó "minified" linkjére kattintva meg is kaptam a beillesztendő kódot

<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>

Ezek után jöhet is a legizgalmasabb része. Azért, hogy a html teljes betöltése után fusson csak le a kód, Feliratkozom a "document" objektum "ready" eseményére.

$(document).ready(function () {
    // majd a lényeg
});

De ennek én a rövidebb formáját szeretem, ami a következő:

$(function () {
    // majd a lényeg
});

A hozzávalóknál említett információk már pár sorból megvannak:

// Az ablak magassága
var windowHeight = $(window).height();
 // Ennyi darab oldal lesz
var numberOfPages = $('#pages .page').length;
// Ekkora lesz az összesített magasság
var contentHeight = windowHeight * numberOfPages;
// És a megjelenítendő oldal indexe az alábbi szerint alakul ki
var activePageIndex = Math.round($(document).scrollTop() / windowHeight);

Itt a legutolsó sor érdemelhet kis magyarázatot. De nézzük egy elméleti példával. A scrollTop metódus azt mondja meg, hogy az oldal a dokumentum tetejéhez képest hány pixellel van lejjebb görgetve. Az belátható, hogy ez sosem éri el a dokumentum magasságát, mivel a megjelenített tartalom magassága ehhez hozzáadódik. Ha például 2500 pixel magas az összes oldal egyszerre, amiből 5 darab van, akkor maximum 2000 pixelig tudok legörgetni. Ha ezt a magassággal elosztom (tehát itt 500 pixel), akkor 4-et kapom eredményül. Mivel az indexek, mint általában, itt is nullával fognak indulni, ez pont az 5. elem megjelenítését jelenti. Minimum pedig nulla pozicióban lehetek, ami természetesen bármivel osztva nullát ad, tehát megvan az első index is.

Persze nem csak egész értékek jöhetnek ki az osztás során, ezért kerekíteni kell az eredményt. Így pedig elég, ha csak megközelítem egy oldal pozicióját, de a kerekítés miatt annak indexét kapom.

jQuery-ben a "$" vagy "jQuery" nevű függvénnyel lekérhetek egy elemet a CSS selectorokhoz hasonlóan. Akárcsak ma a document.querySelector() metódussal. Én itt maradtam a jQuery-nél.

Szinte már kész is van. A tartalom összesített magasságát viszont azért számoltam ki, hogy erre beállíthassam a body-t.

$('body').css('height', contentHeight);

Már csak az "active" osztály kezelése van hátra, ami a következő:

$('#pages .page').removeClass('active');
$('#pages .page:eq('+ activePageIndex + ')').addClass('active');

Az ":eq(index)" arra való, hogy index alapján az előtte levő selectorra illeszkedő valahányadik elemet kérhessem el. Így minden oldalról leveszem az aktív jelzést, és csak egyre teszem vissza. Persze, ha ezt most beilleszteném, a görgetősáv már megjelenne, semmi változást nem látnék görgetésre. Mivel fel kel iratkozni a "scroll" eseményre, ahol ugyanazt kell lefuttatni, mint az oldal betöltésekor, az összes műveletet kiteszem egy függvénybe. Ez alapján így néz ki a teljes javascript egyben:

$(function () {
    function refreshPage() {
        var windowHeight = $(window).height();
        var numberOfPages = $('#pages .page').length;
        var contentHeight = windowHeight * numberOfPages;
        var activePageIndex = Math.round($(document).scrollTop() / windowHeight);

        $('body').css('height', contentHeight);
        $('#pages .page').removeClass('active');
        $('#pages .page:eq('+ activePageIndex + ')').addClass('active');

    }

    refreshPage();
    $(window).scroll(refreshPage);
});

Azért, hogy meglegyen egyben a html-lel együtt, mutatom azt is:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Scroll Pager</title>
    <style>
        .page {
            position: fixed;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            display: none;
        }
        .page.active {
            display: block;
        }
    </style>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
    <script>
        $(function () {
            function refreshPage() {
                var windowHeight = $(window).height();
                var numberOfPages = $('#pages .page').length;
                var contentHeight = windowHeight * numberOfPages;
                var activePageIndex = Math.round($(document).scrollTop() / windowHeight);

                $('body').css('height', contentHeight);
                $('#pages .page').removeClass('active');
                $('#pages .page:eq('+ activePageIndex + ')').addClass('active');

            }

            refreshPage();
            $(window).scroll(refreshPage);
        });
    </script>
</head>
<body>
<div id="pages">
    <div class="page active">Page 1</div>
    <div class="page">Page 2</div>
    <div class="page">Page 3</div>
    <div class="page">Page 4</div>
    <div class="page">Page 5</div>
</div>
</body>
</html>

Ezt így akár már ki is lehet próbálni, és legtöbb esetben működne is. Van azonban egy szépséghibája. Próbáld csak ki, hogy lekicsinyíted a böngészőablakot éppen csak néhány pixelnyire, majd töltsd újra a lapot. Működik a görgetés és villámgyorsan vált az oldalak között. Viszont ezután tedd vissza teljes képernyőre az ablakot. Ha elég nagy volt a különbség a két méret között, akkor most a görgetősávod eltűnt, ami miatt görgetni sem tudsz. Ezért pedig a görgetésre rakott esemény sem rakja helyre az oldalt. A megoldás tehát a "resize" eseményre feliratkozás a "scroll" mintájára:

// ...
refreshPage();
$(window).resize(refreshPage);
$(window).scroll(refreshPage);
// ...

Animációval

Az előző megoldás is teszi a dolgát, de lehet, hogy szeretnék egy kis áttűnés effektet. Kicsit változtatni kell a logikán, mert a display tulajdonság váltogatása helyett az áttűnés mértékét kell módosítanom fokozatosan. Szerencsére erre már van CSS megoldás. Ez pedig a transition

Amikor az active osztályt leveszem egy oldalról, lassan el kell tűnnie. Tehát az alábbi két sort beillesztem a "page" osztály szabályai közé:

opacity: 0;
transition: opacity 0.5s ease-out;

Amihez pedig hozzáadom az osztályt, annak láthatóvá kell válnia. Ezért a ".page.active" szabály az alábbira változik:

opacity: 1;
transition: opacity 0.5s ease-in;

Ezekkel a szabálynak megfelelően teljesen áttetsző, vagy teljesen látható lesz az oldal. A transition-nek köszönhetően viszont fél másodperces sebességgel történik meg az "opacity" értékének megváltoztatása.

Ha viszont az elem gyakorlatilag végig megjelenik, csak az áttetszősége lehet akkora mértékű, hogy nem látszik, akkor mindig a legutolsó oldal lesz legfelül. Azaz egy kattintás eseményre mindig a legfelső oldal reagálna. Ennek kiküszöbölésére az új aktív oldalt az elrejtendő oldal alá kell helyezn a "z-index" segítségével.

.page {
    z-index: -100;
    opacity: 0;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    transition: opacity 0.5s ease-out;
}
.page.active {
    z-index: 0;
    opacity: 1;
    transition: opacity 0.5s ease-in;
}

De, hogy látszon is valami, egy kis stílus még nem árt a page osztálynak:

.page {
    text-align: center;
    font-size: 50pt;
    color: white;
    background: darkgray;

    /* ... */
}

És el is értünk a végleges megoldáshoz. Ezt persze tovább lehet fejleszteni akár, hogy az áttűnés folyamatos legyen, ne csak a váltás pillanatában induljon el, de azzal ebben a cikkben már nem foglalkozom. Kérdés, javaslat, jobb megoldás jöhet kommentben bátran. Végül pedig jöjjön egyben az animált megoldás HTML kódja:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Scroll Pager</title>
    <style>
        .page {
            text-align: center;
            font-size: 50pt;
            color: white;
            background: darkgray;

            z-index: -100;
            opacity: 0;
            position: fixed;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            transition: opacity 0.5s ease-out;
        }
        .page.active {
            z-index: 0;
            opacity: 1;
            transition: opacity 0.5s ease-in;
        }

    </style>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>
        $(function () {
            function refreshPage() {
                var windowHeight = $(window).height();
                var numberOfPages = $('#pages .page').length;
                var contentHeight = windowHeight * numberOfPages;
                var activePageIndex = Math.round($(document).scrollTop() / windowHeight);

                $('body').css('height', contentHeight);
                $('#pages .page').removeClass('active');
                $('#pages .page:eq('+ activePageIndex + ')').addClass('active');

            }

            refreshPage();
            $(window).scroll(function() {
                refreshPage();
            });
        });
    </script>
</head>
<body>
    <div id="pages">
        <div class="page active">Page 1</div>
        <div class="page">Page 2</div>
        <div class="page">Page 3</div>
        <div class="page">Page 4</div>
        <div class="page">Page 5</div>
    </div>
</body>
</html>

Kategóriák: 
Megosztás/Mentés

Hozzászólások

Anonymous képe

Tudom, hogy háttal nem kezdünk mondatot... de én mégis dobtam egy hát-ast! Nagyon jó, de bevallom, hogy a végére elvesztettem a fonalat néha. Sajnos velem van a baj, nem a szövegeddel (mert az tökéletes). Gratulálok, ez nagyon okos segítség a webezőknek, és az is jó, hogy végre ismét van szakmai cikk is.

Rimelek képe

Köszönöm! Lesz és még több szakmai cikk. Csak előbb az e-mail értesítéseket kell rendberakjam, hogy kapjak figyelmeztetést a kommentekről.

Anonymous képe

Egy demo még asszem belefért volna a végére, ill. bár nem olvastam még a cikket, de a cím alapján azt hiszem az egyik posztom apropójára készült a cikk így nekem még meg is van ez viszont a "gyengébbek kedvéért" nem lenne rossz, valamint lehet csak egyéni probléma, annyira nem szimpatikus ez az éppen csak a bankszámlaszámom nem kell megadni-féle kommmentelési lehetőség.

Rimelek képe

Igen, jól emlékszel, mire készült. Mondhatni, neked köszönhetik az olvasók a cikket. A DEMO észrevétel jogos. Korábbi cikkeknél találhatsz olyant is. Itt félig a sietség miatt. A régi helyre nem akartam feltölteni. Igyekszem majd DEMO-kat is készíteni a jövőben.

Ami a kommenteket illeti, az régóta bajos. Nem tagadom. A név megadás kötelezősége egyenesen gyanús. Főleg, hogy meg se jelenik. Szóval ezért is köszönöm az észrevételt!

Anonymous képe

Legalább te megkapod az adataim, vagy csak az utalásból jöttél rá, hogy ki írta?

Rimelek képe

Mindkettő. Bár a név nem jött át.