#CSSfix – Chrome i fixed sidebar w CSS

Tworząc szablon do bloga natrafiłem na dość interesujący problem, który rozpocznie serię krótkich artykułów poświęconych próbie radzenia sobie z dziwnymi zachowaniami stylów CSS.

Zazwyczaj z takimi problemami można zetknąć się na urządzeniach mobilnych, których jest od groma. Mamy przynajmniej kilka mobilnych systemów operacyjnych, do tego dochodzą ich różne wersje, a na tych wersjach można zainstalować różne przeglądarki… w różnych wersjach… a jeszcze są przeglądarki domyślne i nakładki producentów… istny koszmar! Szczęśliwi Ci frontendowcy, którzy mają ograniczony zakres wspieranych urządzeń. Choć czasem nawet najbardziej wypasione przeglądarki i to w najnowszych wersjach potrafią doprowadzić człowieka do wrzenia przed monitorem. O ile jest to JavaScript, to można sobie jakoś z nim poradzić z pomocą polyfilli. Ze stylami CSS bywa czasami trochę gorzej. I dzisiaj będzie o jednym z takich CSS-owych „baboli”.



Podczas projektowaniu bloga wymyśliłem sobie, że będę miał panel boczny, który będzie zawsze widoczny na stronie, nawet gdy użytkownik zacznie scrollować zawartość. Czyli po prostu standardowy pływający element na stronie z ustawionym position: fixed;. W przeszłości miewałem już problemy na starszych androidach i iOSach z takim pozycjonowaniem (skaczące headery itp.), ale przecież mamy 2017 rok więc co może pójść teraz nie tak? Jak zawsze podchodziłem ze sporą ostrożnością do kwestii mobile’a tak teraz odpuściłem. I niestety mobilny Chrome dał mi się we znaki. Zaprojektowany sidebar ustawiłem na 100% wysokości strony i przy scrollowaniu zauważyłem pewien „glicz” pojawiający się na górze sidebara, bądź też na dole (zależy od ustawienia styli). Wszystkiemu winien jest mechanizm Chrome’a, który ukrywa pasek adresowy przeglądarki przy scrollowaniu w dół. Aby nie mówić na sucho postanowiłem stworzyć proste demo prezentujące ten problem: demo. Na desktopie w chromie wszystko wygląda dobrze, ale na mobilnym już niekoniecznie. Wystarczy doprowadzić do sytuacji w której pasek górny się chowa i mignie nam tył strony w miejscu gdzie powinien kończyć się lub rozpoczynać pasek.

Przykład, który przedstawiłem to dwa elementy drzewa DOM obok siebie – problematyczny sidebar z position: fixed; oraz blok reprezentujący długą zawartość strony:

<!DOCTYPE html>
<html lang="pl">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Z podwórka programisty - cssfix position fixed</title>
        <style>
            html, body {
                height: 100%;
                margin: 0;
                padding: 0;
            }

            #content {
                height: 5000px;
            }

            #sidebar {
                position: fixed;
                left: 0;
                top: 0;
                width: 20%;
                background: #444;
                height: 100%;
            }
        </style>
    </head>
    <body>
        <div id="sidebar"></div>
        <div id="content"></div>
    </body>
</html>

Błąd nie jest może jakiś tragiczny, bo przecież nie blokuje nam możliwości korzystania ze strony, no ale wygląda to fatalnie. Efekt jest tym gorszy im szerszego mamy sidebara. Poniżej moje pomysły na poradzenie sobie z tym problemem:

Wykorzystanie viewport units

Moim zdaniem najprostszy i najładniejszy sposób, wystarczy spojrzeć:

#sidebar {
    position: fixed;
    left: 0;
    top: 0;
    width: 20%;
    background: #444;
    height: 100vh;
}

Jedna jednostka vh to 1% wysokości viewportu, czyli obszaru na którym renderowana jest strona WWW (bez paska adresu przeglądarki etc.). Przy tej technice należy pamiętać aby ustawić wysokość znaczników html oraz body na 100%, ponieważ przeglądarki różnie podchodzą do kwestii rozmiarów domyślnych. A jaka jest wada tego rozwiązania? Jeżeli musimy wspierać androidy poniżej wersji 4.4 to musimy posiłkować się JavaScriptem, ponieważ zabrakło dla tych starszych urządzeń wsparcia dla jednostek viewportu.

Rezygnacja z zafiksowanego paska – position: absolute

Miewałem już projekty w których klient rezygnował z elementów pozycjonowanych za pomocą fixed, ponieważ zabierały mu przestrzeń na urządzeniu mobilnym. Warto więc przemyśleć swój projekt i być może wykorzystać position: absolute;, które co prawda nie zapewni nam tego, że element pozostanie na stałe w tym samym miejscu, ale również będzie niejako „oderwany” od strony.

Przewijanie zawartości strony poza znacznikiem <body>

Na początku zmieńmy nieco strukturę naszego przykładowego HTMLa:

<body>
    <div id="sidebar"></div>
    <div id="wrapper">
        <div id="content"></div>
    </div>
</body>

Opakowałem zawartość strony za pomocą elementu #wrapper, który będzie odpowiednikiem znacznika <body>. Teraz czas na style:

body {
    position: relative;
    height: 100%;
    width: 100%;
    overflow: hidden;
}

#sidebar {
    position: absolute;
    left: 0;
    top: 0;
    width: 20%;
    background: #444;
    height: 100%;
}

#wrapper {
    padding-left: 20%;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

#content {
    height: 5000px;
}

Co tutaj się stało? Otóż wyłączyłem możliwość scrollowania strony za pomocą właściwości overflow: hidden; dla elementu <body>. Pasek boczny nie ma już właściwości fixed lecz absolute, rozciągniętą na całą wysokość strony standardowo za pomocą wartości procentowych. Nowością w przykładzie jest element #wrapper, który również posiada pozycje absolutną, jest rozciągnięty na całą stronę i odsunięty od lewej krawędzi na szerokość paska bocznego. Właściwość overflow-y: auto; sprawia, że pojawia się pionowy pasek przewijania gdy zawartość kontenera jest większa niż jego wysokość, a właściwość -webkit-overflow-scrolling: touch; dodaje efektu „momentum” dla systemu iOS i starszych wersji androida. Wreszcie mamy w środku kontenera element #content o dużej wysokośći aby pokazać pasek przewijania. Czy to rozwiązanie ma jakieś wady? Niestety tak. W przypadku dużej ilości animowanych elementów (np. za pomocą transition) na różnych wersjach iOS można zauważyć problemy z renderowaniem i miganie elementów. Czasem pomaga zastosowanie właściwości backface-visibility: hidden;, a czasem po prostu musimy zrezygnować z wodotrysków. Zastosowanie więc tej techniki jest zależne od złożoności naszej strony/aplikacji.

Zastosowanie JSa

Czy to w postaci biblioteki do scrollowania, czy też po prostu do obliczania pozycji paska na stronie. Celowo nie pokazuje tutaj żadnego kodu, ponieważ uważam takie rozwiązanie za czyste zło i jeżeli Ci miły odbiór aplikacji na słabszych urządzeniach to lepiej po prostu jeszcze raz przemyśl swój projekt.

Podsumowanie

W zależności od specyfiki naszego projektu możemy wybrać jedną z powyższych możliwości lub połączyć kilka. Niestety świat CSSa nie jest idealny i pewnie nigdy taki nie będzie, ale z drugiej strony nie można powiedzieć, że jest nudno 😉 Choć nie ukrywam, że miewałem chwile po długim obcowaniu ze stylami CSS w których zastanawiałem się czy backend nie byłby lepszym wyborem… Oczywiście, że nie! 😉

P.S. Podzielcie się proszę swoimi rozwiązaniami tego „dziwnego” problemu na mobilkach, być może i ja dowiem się czegoś nowego. Zapraszam do komentowania! 🙂