Jakiś czas temu projektowałem szablon zaplecza administracyjnego, składającego się z dwóch i trzech kolumn. Ze względów czysto wizualnych oraz zaleceń klienta, kolumny te miały być w każdym możliwym przypadku wyrównane względem siebie, wypełniając 100% wysokości witryny. Problem ten rozwiązałem w dość prosty sposób, wykorzystując pseudo-element :before , który w tym krótkim przykładzie Wam zaprezentuję.
Struktura HTML
Przygotujmy sobie plik HTML witryny z trzema kolumnami. Po prawej i lewej stronie sidebary na menu i różnego rodzaju widżety, a po środku miejsce na treść.
1 2 3 4 5 | <div class="container"> <div class="sidebar_a">Menu po lewej</div> <div class="content">Treść naszej strony</div> <div class="sidebar_b">Widżety po prawej</div> </div> |
Podstawowe style CSS
Kolumny należy teraz odpowiednio ostylować. Zrobimy to za pomocą poniższego kodu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | body { margin: 0; background: #005757; font-family: "Calibri"; color: #fff; } .container { max-width: 100%; margin: 0 auto; } .content { width: 58%; float: left; padding: 1%; background: #001818; } .sidebar_a { width: 18%; float: left; padding: 1%; background: #043535; } .sidebar_b { width: 18%; float: left; padding: 1%; background: #043535; } |
OK, efekt jest zbliżony do takiej wizualizacji:
Kolumny dostosowują się do treści, co w przypadku, kiedy jest ona nierównomierna, wygląda mało estetycznie. Za moment jednak sobie z tym poradzimy, wykorzystując pseudo-element :before z CSS.
Pseudo-element :before
Dla każdej kolumny stworzymy osobną „wyimaginowaną” warstwę, która znajdować się będzie wizualnie pod spodem każdej z nich. Wykorzystując pozycjonowanie absolutne, umiejscowimy je w odpowiednich miejscach witryny, nadając im kolor i szerokość identyczne z kolumnami-matkami. Myślę, że następujący kod wszystko Wam wyjaśni.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /*Pseudo-element :before*/ .content:before { width: 60%; content: ''; position: absolute; top: 0; bottom: 0; z-index: -1; left: 20%; background: #001818; } .sidebar_a:before { width: 20%; content: ''; position: absolute; top: 0; bottom: 0; z-index: -1; left: 0; background: #043535; } .sidebar_b:before { width: 20%; content: ''; position: absolute; top: 0; bottom: 0; z-index: -1; left: 80%; background: #043535; } |
Dzięki z-index: -1; osiągnęliśmy wspomniane już umiejscowienie dopełnienia poniżej warstw właściwych. Należy pamiętać, że jeśli pierwszy sidebar jest szeroki na 20%, to kolejna kolumna (w tym przypadku to .content) powinna być przesunięta w lewo właśnie o te 20%. Kolumna trzecia przesuwana jest natomiast o sumę szerokości kolumny pierwszej i drugiej. Obecny schemat kolumn powinien być od tej pory taki jak poniżej.
Podsumowanie
Nie jestem pewien, czy ten sposób jest najbardziej efektywny, ale na pewno rozwiązuję problem, z którym się borykałem. Jeżeli znacie inne rozwiązania, podzielcie się nimi w komentarzach pod wpisem.
Tagi: 100% • height • promowany • wysokość
Przekombinowane równo :D a wystarczyło użyć wyświetlania tabelkowego + odpowiednio ustawić height: http://bzdety.comandeer.pl/table.html
Jeden z najlepszych sposobów, ale prawdopodobnie nie zadziała z większością gridów.
Jeśli grid nam przeszkadza, zamiast pomagać, to raczej coś jest nie tego ;) To raczej dopasowuje się narzędzie do rozwiązania a nie odwrotnie :P
Oczywiście tylko jest jedno ale. Przy złożonych realizacjach obok strony promocyjnej/informacyjnej, różnych lookbooków, katalogów etc. jest sklep i np. produkty muszą być wyrównane. Nie ma sensu zmieniać całego gridu (ja dla Gridle{scss} równie funkcjonalnej alternatywy nie widzę, ale to pomijam). Trochę się z tym już kopałem i finalnie najlepsze rozwiązania były w JS.
Ok, ale jakie jest wgl szansa, że trzeba będzie łączyć grid z tego typu rozwiązaniem? Zwykle mamy do czynienia z dwiema, w porywach trzema kolumnami, które trzeba wyrównać w długości. W tym wypadku można se po prostu podarować grida ;) Można mieć też po prostu dodatkowy grid (tzn po prostu odziedziczony) – jeśli dla obsługi czegoś takiego należy zmienić cały grid, to IMO coś nie do końca jest z nim w porządku (z założenia takie komponenty powinny być elastyczne). Inna rzecz, że przy gridzie zwykle wystarczy samo height: 100%, bez tabelek (na moim działa bez problemu ;)).
Jeśli jest to tylko estetyczny wybryk, to JS jak najbardziej. W innym wypadku szukałbym rozwiązania w CSS.
Hmmm…. Ale wydaje mi się, że zamiana elementu blokowego na display: table; jest nieelegancka. Po to mamy div, żeby był divem a nie zachowywał się jak tabela.
Niby czemu? Zmieniasz mu semantykę? Nie. Zmieniasz mu jedynie sposób prezentacji a od tego jest przecież CSS. Czemu div wyświetlany jak tabelka Cię razi a tabelka wyświetlana jako div, żeby być responsive już nie? ;)
Należałoby zacząć od tego, że to wcale nie wyrównuje wysokości kolumn. Sama kolumna nadal ma taką wysokość, jaką miała. To tylko kładzie warstwę w takim samym kolorze pod tę kolumnę. A co jeśli kolumna ma tło obrazkowe repeat-y? Wtedy rozwiązanie do kosza.
To rozwiązanie ciągnie za sobą jednak problemy z wydajnością.
overflow: hidden; nie jest renderowane, więc nie ma problemów z wydajnością. Tyle w temacie.
Ermmm… jest. I w tym leży cały problem. To samo się zarzuca position: absolute i pozycjonowaniu poza ekran i dużym text-indent. Przeglądarki naprawdę tworzą te boxy (ah, te bezsensowne nieoptymalizacje).
Inna rzecz, że to i tak jest to wydajniejsze od rozwiązania w JS, które wymaga zabawy ze zdarzeniami resize, orientationchange i przy każdej zmianie wysokości strony (a może się zmienić od wszystkiego ;)). Nie użyjemy debounce’a i już mamy problem.
No jaha, okej, ale w założeniu przecież to, co jest poza widocznością nie zawiera żadnej treści. Pomijając, że to, co nie jest widoczne, nie jest wyrenderowane. Co najwyżej siedzi w pamięci i jeśli wykonuje jakieś akcje, to obciąża procesor, ale nie jest wyrenderowane (i przeglądarka nie musi tego renderować, mam na myśli oczywiście obraz sam w sobie, bo to jest rendering). No więc jeśli nie zawiera żadnej treści, to jedyne co może zawierać to puste tło. A dla przykładu podam pewnie znaną wam stronę:
http://worlds-highest-website.com/pl/
Ma 19 kilometrów, a nie 30k pikseli – a problemów z wydajnością nie ma.
Otworzyłeś na jakimś mobilnym? ;)
30k pikseli to nawet nie jest 1/1000 19 kilometrów, więc nie przesadzaj. Ja uważam, że jeśli to ma stwarzać problem (bo mówimy tu o problemie, a nie o samym obniżeniu wydajności), to większy problem będzie stwarzać transition na :hover.
Tak jakby ktoś chciał przeliczać px na km to niech nawet nie zaczyna… 1 px nie ma rozmiaru w mm. Można określić np. ppi, ale to już zależy od matrycy.
To sobie przelicz, jaką byś musiał mieć matrycę, żeby 30k pikseli to było 19 metrów (1/1000*19km). Nawet najnowsza Retina nie będzie miała takiego wyniku. Tak czy siak, to wszystko to odbieganie od tematu, że 30k pikseli pustego obrazu (i to nie rastrowego obrazka, gdzie faktycznie każdy piksel coś waży w pamięci) nigdy w życiu nie wpłynie zauważalnie na wydajność. Tak jakby ktoś chciał pisać tutorial na wydajne rozwiązanie, używając JS albo N pseudoelementów, gdzie N to liczba kolumn.
> Nawet najnowsza Retina nie będzie miała takiego wyniku.
Chyba nie rozumiesz. Im większe ppi tym mniej „kilometrów”. Retina będzie miała najmniej (tzn, że plamka jest mniejsza, a to jest na plus), a stare Nokie najwięcej.
Czegoś się Pan tak przyczepił do tych kilometrów. Dobrze, pomyliłem się, pisząc z rozpędu. Zamiast podawać argumenty, że 30k pikseli spowoduje problem (powtarzam: problem) w postaci wydajności, to Ty właśnie debatujesz nad tym, czy 30k pikseli będzie, czy nie będzie miało 19 metrów.
We foundation jest data equalizer, w zwykłym kodzie height: 100% bez problemu zadziała :)
To rozwiązanie ma 2 poważne wady:
1) wymusza użycie Foundation albo co najmniej jego części
2) jest zależne od JS – a podstawowy wygląd nigdy nie powinien być od niego zależny
Pytanie czy takie coś jak wyrównanie kolumn w pionie (wyrównanie wysokości) to element 'podstawowego wygladu’? Jak zdefiniować to co jest podstawowym wyglądem a co nie? W przypadku zastosowania JS do rozwiązania tego problemu, problem może być tylko taki, że user wyłączył/nie ma JS i zobaczy kolumny w różnych wysokościach…
Na marginesie – nie pochwalam wykorzystania całego Foundation tylko po to żeby wyrównać kolumny, chodzi mi o sam fakt stosowania JS tak jak tutaj np.: http://css-tricks.com/equal-height-blocks-in-rows/
>Jak zdefiniować to co jest podstawowym wyglądem a co nie?
Jeśli dana strona się rozpadnie albo będzie działać źle, to był to podstawowy wygląd ;) Jeśli bierzemy równe kolumny jedynie za myk estetyczny, to ok – można iść w JS. Pytanie tylko czy warto, skoro rozwiązania w CSS są proste i de facto działają wszędzie (bo nawet w IE8). No i takie wykorzystywanie JS jednak trochę mu po prostu urąga ;)
Dla robiąc layout panelu administracyjnego warto opierać się na jakimś frameworku css więc na pewne wtedy nie wykorzystujemy tylko jednej rzeczy. Dodatkowo – można załadować tylko to co potrzeba :> Niemniej to ostateczność, zawsze najpierw staram się w css.
Equalizer z Foundation daje radę pod warunkiem, że używamy go do prostych rzeczy (bez dynamicznej treści) i używamy Foundation. W moim przypadku ostatnio nie spełniają się oba warunki, więc napisałem własny, bardziej elastyczny kod jako dodatek do jQuery.
Pisany był na szybko, więc jak ktoś znajdzie sposób na ulepszenie to niech da znać. Gwiazdki/stars miło widziane :)
Demo na codepen.io: http://codepen.io/MichalRazorZalecki/pen/flHqw
Gist: https://gist.github.com/MichalRazorZalecki/9b4a79fdba85edf14a53
Plus za debounce (ale czemu tak dziwnie rozwiązany przez customowe zdarzenie?)
Duży minus za niezłapanie wszystkich przypadków zmiany wielkości strony:
1) user zmienia rozmiar ręcznie – jedyny przypadek złapany
2) user zmienia orientację urządzenia i automatycznie następuje zmiana rozmiarów ekranu
3) załadował się marudzący obrazek
4) lazy load
5) spoilers
6) 800 innych przypadków, których nie mam teraz w głowie ;)
Taki scrollspy od Bootstrapa przelicza offsety przy scrollu, ale tutaj i tak to jest niewystarczające (layout rozpadnie się i złoży dopiero po scrollu, co jest niedopuszczalne). Jedynym sensownym rozwiązaniem zostaje po prostu timer (a raczej rAF, żeby nie zarżnąć browsera). I tak wiąże się to z niesamowicie częstym repaintem, którego w CSS da się uniknąć bardzo prosto.
btw this.resizeTimer brudzi global scope – po co?
> czemu tak dziwnie rozwiązany przez customowe zdarzenie?
Performance! + tak mi się spodobało :)
> user zmienia rozmiar ręcznie
Tutaj to już popłynąłeś.
> this.resizeTimer brudzi global scope
Nie ma(?) idealnego rozwiązania. Taki boli mnie najmniej.
> 800 innych przypadków
I po to mam dostępną makeEqual. Badam psss! Ustawię sobie wszystko pod siebie, nie zawsze potrzeba RWD (+ nie wszyscy widzą potrzebę mieć RWD, albo strona pod mobilne to m.********), więc zmiana orientacji nie jest zawsze potrzebna, resize jest uniwersalne.
> załadował się marudzący obrazek, lazy load, spoilers
hm? Tak czy siak mam makeEqual().
>Performance!
Na pewno nie. Rzucenie dodatkowego zdarzenia zamiast czystego debounce’a na pewno nie jest szybsze (sama normalizacja zdarzenia przez jQuery zabija tę optymalizację).
>Nie ma(?) idealnego rozwiązania.
IIFE
>hm?
No przecież obrazki również zmieniają wysokość strony (zwłaszcza te RWD, nie mogące mieć wszak narzuconych z góry rozmiarów). Kiedy taki obrazek się wczyta, wielkość strony automatycznie się zmienia, ale resize event nie odpala się. To samo stanie się w przypadku lazy load, więc przy obrazkach Twój sposób zupełnie nie wypali. Rozwinięcie „natywnego spoilera” (details) nie odpala resize. W sumie mało co odpala resize, więc to tak naprawdę rozwiązanie pod bardzo konkretny przypadek ;) W tym wypadku naprawdę pozostaje timer a to z góry przekreśla dużą wydajność (rAF może ją zapewnić, ale w tym wypadku mamy bardzo ograniczone wsparcie: stare IE odpadają w przedbiegach)
> Rzucenie dodatkowego zdarzenia zamiast czystego debounce’a na pewno nie jest szybsze.
Złoty środek pomiędzy wygodą. Obciążenie jest i tak na tyle małe, że nie ma sensu imo tracić na to czasu. To i tak jest mało znaczący punkt całego rozwiązania. Z założenia ma zadziałać:
A) Po załadowaniu
B) W innych przypadkach gdy tego chcę.
Raki był „brief” i to zostało osiągnięte.
Gdy się wczyta, zawsze można wywołać makeEqual i to załatwia sprawę.
>Złoty środek pomiędzy wygodą.
Czy ja wiem? W tym momencie masz rozbitą obsługę jednego zdarzenia na 2 różne miejsca w kodzie. Resizeend nie jest natywne i uwierz mi, że mało kto będzie jego definicji szukać w handlerze zdarzenia resize.
>Gdy się wczyta, zawsze można wywołać makeEqual i to załatwia sprawę.
Strona czy obrazek? Jeśli to drugie, to ustalenie czy obrazek na pewno się wczytał wcale nie jest takie proste (zwykłe load lubi oszukiwać).
Przede wszystkim to nie jest kompleksowa obsługa layout, a funkcja obliczenia i nadania odpowiednich, równych wysokości. Cały czas można coś poprawić, ale idąc tym tropem to całe życie bym refaktoryzował głupią funkcję, więc nie dajmy się zwariować :)
Jeżeli znasz jakieś gotowe rozwiązania, które spełniają w/w zadania to chętnie poznam.
>Jeżeli znasz jakieś gotowe rozwiązania to chętnie poznam.
Tak, CSS ;) Nie używam JS do layoutu; jedynym wyjątkiem są aplikacje JS-only i to naprawdę heavy JS, gdzie cały layout i tak jest układany przy pomocy custom elements.
Użycie height: 100%/ display: table-cell w 98% przypadków nie nastręcza większych trudności.
Osobiście bym zrobił z tego IINFE i odpalał rekurencyjnie rAF, gdybym już musiał coś takiego zrobić (analogicznie do display systemu w grach/wysokowydajnościowych aplikacjach)
Od kiedy rekurencyjnie to wydajnie? :P
btw. dla mnie RAF to lotnictwo UK, więc jakbyś mógł jaśniej to by było lepiej
>Od kiedy odpalał rekurencyja jest wydajna? :P
To zależy co odpalasz. Chyba nikt nie rzuca się o to, że setTimeout odpalający kolejny setTimeout jest niewydajne ;) Wszystko tak czy siak leci asyncem.
rAF, nie RAF :P requestAnimationFrame, czyli podstawa Animation API, zarówno w CSS, jak i JS
Dobrze, tylko requestAnimationFrame zabije wydajność. U mnie obliczenia zostaną wykonane raz, a u Ciebie nawet do 60 razy na sek (przy monitorze 60Hz).
No właśnie nie – rAF wywoływany jest wtedy, gdy przeglądarka uzna to za stosowne. To nie zabija wydajności, tylko pozwala na płynną animację. setTimeout zabije wydajność, rAF nie powinien.
Poza tym Twoje rozwiązanie nie działa z obrazkami i wymaga dużych zmian, żeby zacząć… Moje, w wypadku gdy repaint ograniczy się tylko i wyłącznie do chwil, gdy zmieniła się wydajność, rozwiązywałoby ten problem i nie powodowało dużego narzutu.
Poza tym – jeśli rAF pozwala na płynną animację zawartości canvas, to do określenia równych kolumn jest wystaraczające
https://gist.github.com/Comandeer/f3d8dc03096b9eb450ab – całkowicie bezobsługowa wersja rAF-owa bez jQuery i z IINFE ;) Oczywiście rozwiązuje problem z artykułu, ale jego odpowiednie przerobienie tak, żeby zamiast dokumentu brał inny container, to kwestia 5 minut (ba, wystarczy podstawić inny element za 1. parametr!)
To rozwiązanie w ogóle nie spełnia moich założeń. Ma podobny efekt, ale dopasowuje się do rodzica. Nie zadziała gdy będę chciał mieć „kafelki” w kilku wierszach, a to podstawowa funkcjonalność przy budowie np. sklepów. Te, które podałem jest bardziej elastyczne.
Problem w tym, że nigdzie nie opisałeś dokładnie swoich założeń… jak już pisałem, moje rozwiązanie obsługuje problem z tematu. Dorobienie do tego sprawdzania wysokości boxów a nie samego kontenera jest kwestią kilku minut a problem obrazków wciąż jest lepiej rozwiązany niż u Ciebie.
Poza tym jeśli potrzebujesz kafelek w kilku rzędach, nie widzę sensu w rozwiązaniu opartym na JS. tutaj idealnie pasuje css grid/wyświetlanie tabelkowe i rozwiązanie w js zawsze będzie obskurnym, mniej wydajnym hackiem. To używanie js do tego, do czego nie został zaprojektowany. To nie czasy ie7, żeby stosować takie hacki…
> problem obrazków wciąż jest lepiej rozwiązany niż u Ciebie
Nie rozumiemy się. Ja nie mam czegoś takiego jak problem obrazków. Ty stworzyłeś taki problem. Ja mam funkcję, która ustawia odpowiednia wysokość elementów. Jak chcę to zawsze mogę zrobić
requestAnimationFrame(function() {
$boxes.makeEqual();
});
I mam pozamiatane. To jest właśnie elastyczność.
Eh, w sklepie nie masz obrazków? Poza tym elastyczne = przewidujące. Inaczej jest rozwiązaniem profilowanym.
Nie widzę w tym „elastyczności”. To w dalszym ciągu hack w js. Dla mnie takie rozwiązanie jest gorsze niźli analogiczne w CSS. Zawsze będzie powodować więcej repaintów niż to konieczne. Nie rozumiem czemu tak bardzo unikasz rozwiązania w css.
[] [] [] []
[] [] [] []
[] [] []
Pokaż jak to wyrównać w CSS.
http://codepen.io/imohkay/pen/gpard – proszę bardzo ;) Pewnie jeszcze fajniej rozwiązuje to CSS Grids.
I zanim zaczniesz marudzić, że nie wszystko obsługuje flexbox: a kto nie obsługuje, oprócz starych IE? Nie mam zamiaru dostosowywać strony do mniejszości. Jeśli mówimy o estetycznym smaczku można se go podarować… Albo zastosować komentarze warunkowe dla IE lub – jeszcze lepiej – feature detection (wystarczy stworzyć bogus div, dać mu display: flex i zobaczyć czy ma taką wartość) i wczytanie fallbacku (np. przez słynną funkcję loadCSS od Filament Group czy też has.js/yepnope). W tym momencie wszędzie tam, gdzie flexbox jest obsługiwany nie ma żadnych problemów z JS (odpowiedni moduł nawet się nie zassie) a stare browsery dostaną polyfilla. Sytuacja win-win, gdzie natywny mechanizm działa tam, gdzie to możliwe a reszta dostaje ekwiwalent w JS.
> Nie mam zamiaru dostosowywać strony do mniejszości.
W praktyce działa to tak, że to nie dev podejmuje decyzje biznesowe. Niemniej jednak Karen Menezes się napracowała tworząc tego pena.
>W praktyce działa to tak, że to nie dev podejmuje decyzje biznesowe.
Jeśli decyzje biznesowe podejmuje „boss” bez udziału devów i każe mu robić shit, bo „działa”, to sorry, ale raczej nie tak powinna wyglądać współpraca. A takie działanie pomoże w przyszłości łatwiej utrzymać całą stronę (bo np zostanie 1% klientów nieobsługujących flexboxa i po prostu z biznesowego punktu widzenia nie opłaca się ich wspierać). Dialog to podstawa współpracy. Dlatego wolę być swoim własnym szefem, bo wówczas jeśli decyzja biznesowa była błędna, to mogę zwolnić jedynie siebie samego… i znaleźć kolejne ja do współpracy ;)
> Niemniej jednak Karen Menezes się napracowała tworząc tego pena.
Nie umniejszając Karen, nie jest to aż tak trudne. Flexbox to chyba najlepiej przemyślana rzecz z CSS3 i to po prostu widać ;) Osobiście gridy trzymam już tylko w nim, z małym fallbackiem dla IE.
a może padding-bottom: 10000px; margin-bottom: -10000px; dla każdej kolumny i overflow: hidden dla kontenera?
Sprawdź inne komentarze :D
hej może coś nie skumałem ale dlaczego nie chciałeś użyć atrybutu min-height i pobawić się wartościami np: {min-height:600px} ?
ja tak na szybkiego zrobiłem …i jakoś ok ;)
Kolumny
#pierwszy{width:30%;
min-height:600px;
background-color:blue;
float:left;}
#drugi{width:30%;
min-height:600px;
background-color:red;
float:left;}
#trzeci{width:30%;
min-height:600px;
background-color:yellow;
float:left;}
A co jak jedna kolumna będzie miała więcej treści niż 600px? Reszta się nie dopasuje i jest problem.
No tak tego nie przewidziałem ;)
A może takie rozwiązanie -> atrybut align-items: i jako wartość stretch.
U mnie …it works! ;)
a kod, który sprawdzałem to ten:
Kolumny
#main{display: flex;
align-items:stretch;
min-height:10px;
}
#pierwszy{width:30%;
min-height:100%;
background-color:blue;
margin:1px;
}
#drugi{width:30%;
min-height:100%;
background-color:red;
margin:1px;
}
#trzeci{width:30%;
min-height:100%;
background-color:yellow;
margin:1px;
}
a
a
aaa
Toć podałem to rozwiązanie 2 tygodnie temu ;) Problemem jest ograniczone wsparcie przeglądarek („tylko” 80%)
A bo nie czytałem wszystkich wypowiedzi od a do z …tylko widziałem jakieś długie dyskusje ;). W takim razie chapeau bas!
a atrybut align -items:stretch ? Nie rozwiąże problemu?
Wiem, że temat przestarzały ale może komuś się przydać i jak tu wpadnie to najprościej użyć display:table-cell i po problemie, nie wiem czemu o tym nikt tutaj nie wspomniał, bo z tego co mi się wydaje opcja ta jest dostępna już od dawna :)