Popupy

Tworzenie popupów wcale nie jest jakimś wielkim wyczynem i w wielu przypadkach łatwiej jest zastosować własny kod, niż używać oddzielnych pluginów (szczególnie gdy takowe trzeba dostosowywać do specyficznych sytuacji). W tym artykule zajmiemy się tym tematem tworzenia okienek, oraz poznamy kilka trików z nimi związanych.

Załączamy biliotekę jQuery do naszej strony i bierzemy się za pracę. Zacznijmy od prostego kodu html naszego popupa:

<div class="popup">
    <div class="bg"></div>
        <div class="container">
            <h3>Przykładowy nagłówek popupa</h3>
            <div class="content">            
                <p>
                Lorem ipsum dolor sit amet, consectetur adipisicing elit...
                </p>
            </div> 
            <div class="buttons">
                <a href="#" class="button cancel">Anuluj</a>
                <a href="potwierzenie-ok.html" class="button ok">OK</a>
            </div>
        </div><!-- e: container -->
</div><!-- e: popup -->

Z czego składa się nasz twór? Mamy tło .bg, które będzie zasłaniać cały ekran. Mamy też .container naszego popupa, czyli wycentrowane okienko. W okienku znajduje się jego zawartość z nagłówkiem H3 oraz przykładowymi przyciskami.

Podstawowe stylowanie naszego okienka ma postać:

@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300,700);

html, body {    
    height:100%;
    background:#000 url(http://subtlepatterns.com/patterns/wood_pattern.png);
}

* {
    box-sizing:border-box;
    -webkit-box-sizing:border-box;
    -moz-box-sizing:border-box;
}


/* popup */
.popup {   
    display:none;
    position: fixed; /* na sztywno ze stroną */
    top:0;
    left:0;
    width:100%;
    height:100%; 
    z-index:1000;
    text-align: center;
}
/* tło popupa */
.popup .bg {
    position: absolute;
    top:0;
    left:0;
    bottom:0;
    width:100%;
    height:100%;

    background: #000; /* ie8 i jego rodzina */
    filter:alpha(opacity=70);

    background:rgba(0,0,0,0.7); /* reszta przeglądarek */
    opacity:1;    
    box-shadow:inset 0 0 100px rgba(0,0,0,1);

    transition:1s;
    -moz-transition:1s;
    -webkit-transition:1s;
}
.popup:after { /* sztuczka dla centrowania */
    content: '';
    display: inline-block;
    height: 100%;
    vertical-align: middle;
    margin-right: -0.25em;
}
/* okienko popupa */
.popup .container { 
    position: relative;                

    /* nasze okienko musimy jeszcze wycentrować */

    padding:20px 30px 30px 30px;  
    font-family:'Open Sans', sans-serif;
    color:#333;
    background: #ffffff;
    background: -moz-linear-gradient(top,  #ffffff 0%, #e5e5e5 100%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#e5e5e5));
    background: -webkit-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%);
    background: -o-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%);
    background: -ms-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%);
    background: linear-gradient(to bottom,  #ffffff 0%,#e5e5e5 100%);
    border-radius:6px;
    box-shadow:inset 0 1px 0 #fff, inset 0 0 30px #ddd, 0 0 20px rgba(0,0,0,0.5), 0 1px 5px rgba(0,0,0,0.5);
    text-align:center;
    transition:1s 1s;
    -moz-transition:1s 1s;
    -webkit-transition:1s 1s;    
}
.popup .container h3 {
    font:300 1.8em 'Open Sans', sans-serif;
    text-transform: uppercase;    
    margin:0;
    padding:0 0 10px 0;
    color:#666;
    border-bottom:1px solid #ddd;
    box-shadow:0 1px 0 #fff;
}
.popup .container .content {
    padding:20px;
    color:#666;
}

/* przyciski w popupie */
.button {   
    display: inline-block;
    background: #f1e767;
    background: -moz-linear-gradient(top,  #f1e767 0%, #feb645 100%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f1e767), color-stop(100%,#feb645));
    background: -webkit-linear-gradient(top,  #f1e767 0%,#feb645 100%);
    background: -o-linear-gradient(top,  #f1e767 0%,#feb645 100%);
    background: -ms-linear-gradient(top,  #f1e767 0%,#feb645 100%);
    background: linear-gradient(to bottom,  #f1e767 0%,#feb645 100%);
    border-radius:3px;  
    min-width:150px;
    padding:1em 2em;
    font:bold 1em 'Open Sans', sans-serif;
    text-transform: uppercase;
    color:#222;
    text-shadow:1px 1px 1px rgba(255,255,255,0.6);
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    margin:0 10px;
    -moz-transition: 0.2s;
    -webkit-transition: 0.2s;
    transition: 0.2s;
    box-shadow:inset 0 1px 0 rgba(255,255,255,0.5), inset 0 -1px 0 rgba(255,255,255,0.3), 0 2px 2px rgba(0,0,0,.1);
}
.button:focus, .button:active {
    box-shadow:inset 0 3px 3px rgba(0,0,0,0.5);
}
.button.cancel {
    background: #dddddd;
    background: -moz-linear-gradient(top,  #dddddd 0%, #aaaaaa 100%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#dddddd), color-stop(100%,#aaaaaa));
    background: -webkit-linear-gradient(top,  #dddddd 0%,#aaaaaa 100%);
    background: -o-linear-gradient(top,  #dddddd 0%,#aaaaaa 100%);
    background: -ms-linear-gradient(top,  #dddddd 0%,#aaaaaa 100%);
    background: linear-gradient(to bottom,  #dddddd 0%,#aaaaaa 100%);
}
/* ie8 fix */
.lt-ie9 .popup .container {
    border:2px solid #111;
}
.lt-ie9 .button {
    border:1px solid #F2D16A;
}
.lt-ie9 .button.cancel {
    border:1px solid #CBCBCB;    
}

Warstwa popupa i tło dostały pozycjonowanie fixed oraz width i height ustawione na 100%, czyli zawsze będą zakrywać cały ekran. Ważne też jest, by naszemu popupowi dać odpowiednio duży z-index. Chcemy, by był zawsze na wierzchu strony.

Do stworzenia półprzezroczystego tła zastosowaliśmy dodatkowy element .bg. Takie tło moglibyśmy stworzyć za pomocą koloru rgba dla .popup, ale IE8 i podobne nie obsługują takich kolorów. Dodatkowo mielibyśmy wtedy mniejsze pole manewru. W tym przypadku zalecam więc zastosowanie dodatkowego elementu .bg. Nie jest to duży koszt, a przynajmniej mamy spokój. Testowałem tutaj też zastosowanie pseudo selektorów :after i :before, ale w połączeniu z filter (opacity) IE8 nie dawało rady. Sprawdzało się tutaj zastosowanie zakodowanego do base64 1 pikselowego png, ale to też działanie trochę na siłę, zwłaszcza, że zmiana koloru czy krycia takiego tła jest niemiłosiernie upierdliwa.

Zauważ, że stylowanie rozpoczęłem od przyjemnej sztuczki

* {
    box-sizing:border-box;
    -webkit-box-sizing:border-box;
    -moz-box-sizing:border-box;
}

Więcej na jej temat przeczytasz pod tym adresem.http://www.paulirish.com/2012/box-sizing-border-box-ftw/. Ogólnie polecam stosować – szczegolnie, gdy interesują cię responsywne layouty.

Jeżeli chcesz się chwalić przed dziewczyną/chłopakiem czymś nowym, już ci tłumaczę o co w tym chodzi. Organizacja W3C jak pewnie wiesz zajmuje się standardami sieciowymi. Wśród tych standardów istnieje jeden zwący się „box-model”.Dokładnie chodzi o sposób wyliczania wielkości elementów na stronie. W tym box-modelelu padding i wielkość bordera dodawane są do zadeklarowanego rozmiaru.

Realna szerokośc boxa = zadeklarowana szerkość boxa - padding lewy - padding prawy - border lewy - border prawy
Realna wysokośc boxa = zadeklarowana wysokość boxa - padding górny - padding dolny - border górny - border dolny

Czyli jeżeli przykładowo mamy box, który ma width:200px, padding:20px; border:2px solid #ddd; to tak naprawdę box taki zajmuje szerokość równą 244 piksele; W praktyce webmasterzy takie wyliczenia robią z automatu. Gdy dziewczyna wysyła ich do sklepu bo 2m wstążki, często kupują 1,44m. Bywa…

Microsoft miał jednak gdzieś standardy i używał w swoich przeglądarkach własnego modelu wyliczania wielkości, który całkiem znacząco różnił się od standardów. No więc w tym box modelu jeżeli chciałeś uzyskać realny rozmiar boxu np 200px x 100px to ustawiałeś mu… właśnie takie rozmiary. Padding i border był w środku i nie wpływał na rozmiary boxa. Więcej na ten temat możesz poczytać na wikipedii :)

Dzięki wspomnianej sztuczce wracamy do microsoftowego sposobu wyliczania rozmiarów i nie nie musimy już nic wyliczać. Ma to szczególne znaczenie w przypadku layoutów responsywnych, ponieważ tam często gęsto takie wyliczenia są niemożliwe do wykonania (chyba tylko za pomocą właściwości calc).

Aha – IE7 i wcześniejsze nie obsługują tej sztuczki, ale takich przeglądarek już nie ma i przy tym zostańmy… Zresztą spokojnie możemy zrobić odpowiednie korekty za pomocą komentarzy warunkowych.

Centrowanie popupa

Aby wycentrować okienko popupa na ekranie, możemy skorzystać z kilku metod. Poniżej omówimy każdą z nich.

Metoda 1 – ujemne marginesy

Pierwszą metodą centrowania okienka jest staroświecki sposób z zastosowaniem ujemnych marginesów.
Ustawiamy absolutną pozycję okienka na left:50%; top:50%, a następnie korygujemy ją za pomocą ujemnych marginesów będących połową rozmiarów okienka (w naszym przykładzie -260px i -115px).

.popup .container {
    /* ...reszta stylowania... */
    position:absolute;
    top:50%;
    left:50%;
    width:480px;
    height:260px;
    margin-left:-240px;
    margin-top:-130px; 
}

Metoda ta sprawdza się właściwie tylko w przypadku sztywnych popupów, które stosujemy na typowo desktopowych stronach. Wbrew pozorom jest to najczęściej spotykana liczba popupów :)
Niestety dla marginesów użycie procentów nie zadziała, czyli nie możemy korygować położenia przez margin:-50% -50%. Jesteśmy więc zmuszeniu na użycie sztywnych pikselowych miar dla naszego okienka.
My lubimy responsywność, więc pominęliśmy tą metodę.

Metoda 2 – translate

Aby naprawić powyższą niedogodność, zamiast ujemnych marginesów możemy zastosować translate. Dzięki użyciu procentowych przesunięć możemy pozbyć się sztywnych rozmiarów dla okienka:

.popup .container {   
    /* ...reszta stylowania... */ 
    position: absolute;
    top:50%;
    left:50%;    
    max-width:700px;
    width:80%;
    min-height: 20%;
    -webkit-transform: translate(-50%, -50%);
    -moz-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);    
}

Metoda 3 – vertical align i display:inline-block

Obsługa właściwości translate jest całkiem dobra. Jeżeli jednak i takie wsparcie nam nie wystarcza, wtedy będziemy musieli skorzystać ze sztuczki, którą wykorzystaliśmy w naszym początkowym kodzie:

.popup {
    /* ...reszta stylowania... */
    text-align:center;
}
.popup:after {    
    content: '';
    display: inline-block;
    height: 100%;
    vertical-align: middle;
    margin-right: -0.25em;
}

.popup .container {
    /* ...reszta stylowania... */
    position: relative;
    display: inline-block;
    vertical-align: middle;
    max-width:700px;
    width:80%;
    min-height: 20%;    
}

Zastosowaliśmy tutaj trik z vertical-align.

Sporo ludzi myli działanie tej właściwości, więc poniżej kolejna porcja informacji do wykorzystania w rozmowie ze swoją dziewczyną/chłopakiem:

Jeżeli vertical-align zastosujemy dla elementu table-cell (czyli komórka tabeli), wtedy właściwość vertical-align zadziała tak jak przewidujemy – będzie centrować zawartość tego elementu w pionie (entuzjaści layoutów budowanych na tabelkach będą zadowoleni).

Jednak dla elementów inline (np. inline-block) vertical-align działa w odmienny sposób, wyrównując względem siebie elementy które leżą obok siebie w jednej linii i mają różną wysokość. Niski element będzie ułożony centralnie w pionie w stosunku do wysokiego elementu.

Dobre omówienie tej techniki znajdziecie np tutaj: http://css-tricks.com/centering-in-the-unknown/
W naszym przykładzie za pomocą selektora :after stworzyliśmy wysoki na 100% element, do którego wyrównaliśmy „niskie” okienko popupa. Ot taki trik.

Centrujemy nasze okienko

Wybieramy metodę trzecią. Okienko popupa dostało nowe style:

/* okienko popupa */
.popup .container { 
    position: relative;
                
    /* centrowanie popupa */    
    display: inline-block;
    vertical-align: middle;
    max-width:600px;
    width:80%;
    min-height: 20%;      

    padding:20px 30px 30px 30px;  
    font-family:'Open Sans', sans-serif;
    color:#333;
    background: #ffffff;
    background: -moz-linear-gradient(top,  #ffffff 0%, #e5e5e5 100%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#e5e5e5));
    background: -webkit-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%);
    background: -o-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%);
    background: -ms-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%);
    background: linear-gradient(to bottom,  #ffffff 0%,#e5e5e5 100%);
    border-radius:6px;
    box-shadow:inset 0 1px 0 #fff, inset 0 0 30px #ddd, 0 0 20px rgba(0,0,0,0.5), 0 1px 5px rgba(0,0,0,0.5);
    text-align:center;
    transition:1s 1s;
    -moz-transition:1s 1s;
    -webkit-transition:1s 1s;    
}

Popup wersja 1

Wszystko wydaje się działać dobrze. Nasze okienko dopasowuje się do swojej zawartości i zawsze jest wycentrowane na środku strony. Co jednak sie stanie, gdy nasz popup będzie miał tyle treści, że mimo chęci
jego wysokość będzie większa od wysokości urządzenia? Zmniejsz okno przeglądarki i przekonaj się sam.

Rozwiązania tego problemu jest kilka. Możemy na przykład bezpośrednio do okienka z komunikatem (.popup .container) dodać dodatkowy div, który będzie miał odpowiednio ustawione overflow:auto. Dzięki temu będzie możliwość przewijania tylko zawartości okienka. Z tego rozwiązania korzysta między innymi FancyBox.

Overflow możemy też dać dla całego elementu .popup. W tym przypadku niestety musimy zrezygnować z elementu .bg, ponieważ element ten przykryje paski przewijania .popupa. Rezygnując z elementu tła wracamy do wymienionych już w tym artykule jej minusów. Na starszych przeglądarkach popup albo nie będzie miał tła, albo zastosujemy wspomnianą wcześniej technikę 1 pikselowego png.

Istnieje też trzecia technika, z której skorzystamy (bo lubimy wybierać trzecie możliwości). Okienko .container okrywamy dodatkowym divem (.container-wrapper), któremu nadajemy overflow. Tak samo jak tło popupa ten div będzie rozciagnięty na całą powierzchnię okna. Traktuj go jako dodatkową warstwę popupa, którą nakładamy na warstwę z tłem.

warstwy-popupa

Nasze stylowanie będzie musiało ciut się zmienić. Dokładnie chodzi o przeniesienie
sztuczki z centrowaniem okienka na .container-wraper

<div class="popup">
    <div class="bg"></div>
    <div class="container-wraper">
        <div class="container">
            <h3>Przykładowy nagłówek popupa</h3>
            <div class="content">            
                <p>
                Lorem ipsum dolor sit amet, consectetur adipisicing elit...
                </p>
            </div> 
            <div class="buttons">
                <a href="#" class="button cancel popup-close">Anuluj</a>
                <a href="potwierzenie-ok.html" class="button ok">OK</a>
            </div>
        </div>
    </div>
</div><!-- e: popup -->

Oraz zmiany w CSS:


/* popup */
.popup {   
    display:none;
    position: fixed; /* na sztywno ze stroną */
    top:0;
    left:0;
    width:100%;
    height:100%; 
    z-index:1000;
}
/* tło popupa */
.popup .bg {
    /* tak jak było */
}
.popup .container-wraper {
    position: absolute;
    top:0;
    left:0;
    width:100%;
    height:100%;
    overflow-x:auto;
    overflow:scroll;
    text-align:center;
}
.popup .container-wraper:after {    
    content: '';
    display: inline-block;
    height: 100%;
    vertical-align: middle;
    margin-right: -0.25em;
}
.popup .container {
    /* ...reszta stylowania... */
    position: relative;
    display: inline-block;
    vertical-align: middle;
    max-width:700px;
    width:80%;
    min-height: 20%;    
}

Zamiast samego overflow:auto ustawiliśmy mu overflow-x:auto; overflow:scroll;. Dzięki temu po pokazaniu się nie będzie skoków treści strony (z wersji z paskiem przewijania i bez paska).

Czemu po prostu nie skorzystać z overflow:auto?

Gdy teraz zmniejszysz okno przeglądarki, a twój popup będzie wystawał poza ekran, pojawi się dodatkowy pasek przewijania po jego prawej stronie. Mamy więc dwa – pasek od strony i pasek od popupa. Dorzucimy trzeci i będziemy mieli dresy, ale przecież nie o to nam chodzi.

Aby ukryć ten pasek i sprawić w na czas pokazyania popupa strona przewijała się tylko w jego zakresie, musimy dodać klasę do body:

body.popup-show {
    overflow: hidden;
    margin-right:17px;
}

Ogranicza ona widoczność reszty strony do jednego ekranu (overflow:hidden) – tym samym ukrywa jej pasek przewijania. Margin-right:17px spowoduje, że storna nie będzie dziwnie skakać.
Po zamknięciu popupa usuniemy tą klasę.

Podpięcie skryptów

Oskryptowanie naszego popupa jest najprostszą rzeczą w całym tym artykule (którą zresztą już stosowaliśmy gdy na siłę poprawialiśmy świat informacjami o cookie)

Na początku stwórzmy jakiś przykładowy element, po kliknięciu którego wywołamy nasz popup:

<a href="#" id="popupTest">TEST POPUPA</a>
$(function() {
    /* element testowy wywołujący popup */
    $('#popupTest').on('click', function(e) {
        e.preventDefault();
        $('body').addClass('popup-show');
        $('.popup').show();
    })

    /* popup */
    $(document).on('keyup', function(e) {
        e.preventDefault();
        if (e.keyCode == 27) {
            $('.popup-close').click();
            return false;
        }
        return true;
    });
    $('.popup-close')
        .attr('title', 'Zamknij popup')
        .css('cursor', 'pointer')
        .on('click', function(e) {
            e.preventDefault();
            $(this).closest('.popup').fadeOut(function() {
                $('body').removeClass('popup-show');
            });
        });    
})

Po kliknięciu na element testowy wywołaliśmy popupa i nadaliśmy body klasę .popup-show.
Jak widzisz, zamknięcie popupa będzie się odbywać po kliknięciu na elementy z klasą .popup-close (które to wywołujemy też za pomocą klawisza ESC), które znajdują się we wnętrzu popupa. Póki co nie mamy takich elementów, ale nic nie stoi na przeszkodzie, by wybranym elementom taką klasę dodać. Ja polecam dorzucić ją do przycisku, który będzie zamykać nasze okienko (u nas guzik .cancel. Do .container-wrapper raczej bym jej nie dawał, bo w przypadku przewijania małego widoku zamykalibyśmy popup (na mobilkach przecież nie ma kółka od myszki).

<div class="popup">
    <div class="bg"></div>
    <div class="container-wraper">
        <div class="container">
            <h3>Przykładowy nagłówek popupa</h3>
            <div class="content">            
                <p>
                Lorem ipsum dolor sit amet, consectetur adipisicing elit...
                </p>
            </div> 
            <div class="buttons">                
                <a href="#" class="button cancel popup-close">OK</a>
                <a href="potwierzenie-ok.html" class="button ok">OK</a>
            </div>
        </div>
    </div>
</div><!-- e: popup -->

Popup wersja 2

Popupy dla urządzeń mobilnych

Tak. To też bardzo ciekawy temat. Dlatego zajmiemy się nim w następnym odcinku :)

Teraz przyszedł czas na podsumowanie.
Powyżej zademonstrowałem wam idee, sposób podejścia do tworzenia okienek informacyjnych. Zdaję sobie sprawę, że na rynku istnieje miliony gotowych rozwiązań itp. Ale nauki nigdy za wiele – prawda?
Oczywiście jak zawsze polecam wam własne eksperymentowanie i dalsze pogłębianie wiedzy na powyższy temat. Może zechcecnie dodać do naszego okienka dodatkowe animacje CSS? Może dorzucicie do niego http://css-tricks.com/receding-background-modal-boxes/ dodatkowe ciekawe efekty? Wszystko zależy od was samych.

Komentarze

  • alufers

    Trochę nie na temat:
    Mógłbyś zrobić tutorial jak wykonać layout (szablon) strony?

    • kartofelek007

      Graficzny + cięcie?

      • alufers

        Tak :)

      • kartofelek007

        najbliższy tutorial będzie o multiselektach, potem mobilne popupy, a potem dopiero laje. Ten temat jest dość spory i będzie obejmował całokształt pracy, nie tylko „rysowanie”.

  • Mariu

    Jak zrobić by klikając w gdziekolwiek poza popup on się zamykał ?

    • kartofelek007

      Dodać klasę .popup-close do .container-wraper (wersja 2) lub do .bg (wersja 1)

  • Mariu

    W tedy klikając w popup też się zamyka, chodziło mi by zamykał się klikając poza nim tylko

    • kartofelek007

      Dodaj na początku .container-wraper dodatkowy div i stylowanie:

      .popup .container-wraper > .popup-close {
      position: fixed;
      top:0; left:0;
      width:100%;
      height:100%;
      }

      Tutaj demo: http://doman.art.pl/webmaster/dema/popupy/popup-wersja2b.html

      • Mariu

        Dzięki, teraz działa jak chciałem, zmieniłem pozycjonowanie z fixed na absolutne kalasy popup-close, Świetny artykuł !