I vår förra artikel tittade vi lite närmare på en av metoderna för att skapa layouter med CSS: absolut positionering. Idag tänkte vi försöka belysa ett annat alternativ, nämligen flytande element (floats). Minst lika missförstått som absolut positionering, men oftast mycket mer användbart.
Allting flyter inte
Panta rhei,
sade
Herakleitos, allting flyter. Även om den
bakomliggande filosofin, att allting befinner sig i oavbruten förändring, i
allra högsta grad gäller för webben, så gäller det inte för dagens ämne. För att
få ett flytande element måste vi uttryckligen ange det, exempelvis så här:
float:left
När vi gör ett element flytande ber vi webbläsaren att knuffa det i sidled, antingen åt höger eller åt vänster, så långt det går.
Den som verkligen förstår vad föregående mening innebär vet redan hälften av vad man behöver veta för att kunna använda flytande element. Det finns tre viktiga bitar information i den där meningen, så låt oss undersöka dem innan vi ger oss på den andra, lite svårare halvan.
Ta ett steg åt sidan
Det första vi behöver lägga på minnet är att ett flytande element förskjuts antingen till höger eller till vänster. Det är alltså inte möjligt att få ett element att flyta i mitten, något som ofta frustrerar nybörjare. Om man tänker efter en stund så är det dock ganska självklart. Varför? För att svara på det måste vi gå händelserna en liten aning i förväg och titta på vad som händer när vi skapar ett flytande element i vårt dokument.
Ett vanligt användningsområde för float
är bilder.
Vi vill kunna lägga in en bild och få texten att flyta runt den. Med
CSS är det dock inte texten vi gör flytande, utan bilden. Om bilden
har float:left
kommer texten att flyta förbi på dess
högra sida. Har bilden float:right
flyter texten
förbi på dess vänstra sida.
Om det skulle vara möjligt att få bilden att flyta i mitten skulle webbläsaren alltså behöva få texten att flyta på två sidor om den. Till skillnad från de två enklare fallen är det långt ifrån självklart hur det skulle gå till. Ska textraderna delas för att ge plats för bilden? Eller ska texten plötsligt delas upp i två spalter? Om inte bilden är väldigt liten är det också en klar risk för att utrymmet på sidan om inte räcker till för långa ord.
Den andra viktiga informationsbiten i grundregeln ovan är att ett flytande
element bara förskjuts i sidled. Vi har sett optimistiska försök att sätta
float:top
för att flytta något längst upp på
sidan.
För att verkligen förstå teorin bakom flytande element måste man först förstå vad en line box i CSS innebär. Tyvärr kräver det i sin tur att man begriper vad som menas med en inline box, vilket är alldeles för komplicerat för att gå in på i detalj. Den som är intresserad (och har en viss masochistisk böjelse) kan försöka dechiffrera det som sägs i avsnitt 9.2.2 av specifikationen för CSS2.1.
En inline box genereras av de element som inte är
blockelement, exempelvis EM
. Dessutom genereras en anonym
inline box av textnoder inuti blockelement. Det är inte så
svårt att tänka sig att varje blockelement består av en rektangulär ruta. En
inline box kan också ses som en sådan ruta, även om den
lyder under lite andra lagar än kusinen block box.
En line box är en tänkt rektangel som omsluter alla inline boxes som bildar en rad i det omgivande blockelementet. Den är (minst) lika hög som sin högsta ingående line box.
Vi gräver inte djupare i boxteorin än så här, utan nöjer oss med föreställningen om att en line box är en tänkt ruta som omsluter en hel rad av text och (ickeflytande) bilder. Orsaken till den här utvikningen är att vi behöver känna till att överkanten på ett flytande element justeras mot överkanten på aktuell line box (eller mot botten av föregående block box om det inte finns någon line box).
Det är också bra att känna till att alla flytande element automatiskt blir en
block box. Det är alltså inte nödvändigt att ange
display:block
för ett flytande element. Mer om det
senare.
Jaha, och vad betyder det på ren svenska då? Jo, att ett flytande element aldrig kan hamna högre än överkanten på den rad där det förekommer. Eller med andra ord, att ett flytande element bara kan skiftas i sidled, men det visste vi ju redan.
Hit men inte längre
Den tredje viktiga detaljen i grundregeln ovan är orden så långt det
går
. När vi gör ett element flytande förskjuts det till höger eller vänster
tills dess kant når kanten på det omgivande blockelementet. Om vi sedan gör ett
annat, närbeläget element flytande åt samma håll förskjuts det tills dess kant
når kanten på det första flytande elementet. Med andra ord, det lägger sig vid
sidan av det första elementet. Gör vi fler element flytande åt samma håll fylls
raden på, men förr eller senare tar utrymmet slut. Vad som händer då ska vi
strax titta på, men först är det dags för en dos teori igen.
Vad är det egentligen som händer?
Precis som med absolut positionerade element tas ett flytande element bort ur dokumentflödet. Det påverkar inte efterföljande blockelement. Däremot förkortas de line boxes som skapas bredvid det flytande elementet. Vi kan föreställa oss att de helt enkelt lämnar plats i sidled.
Säg att vi har ett antal stycken med text. I det första stycket vill vi
infoga en hög, men smal bild och vi vill att texten ska flyta på höger sida om
bilden. Vi sätter alltså float:left
på bilden.
De line boxes som genereras av texten i första stycket förkortas så att de börjar vid bildens högerkant och sträcker sig till styckets högra kant. Men bilden är så hög att texten i första stycket tar slut innan den nått bildens nederkant.
Nästa stycke, som ju är ett blockelement, tar ingen hänsyn till den flytande bilden. Den har ju tagits bort ur dokumentflödet. Dess block box skapas alltså på vanligt avstånd från det första styckets block box, med hänsyn taget till marginaler och sådant. Här kommer det fina i kråksången: det andra styckets line boxes förkortas också, precis som i det första stycket.
Så fortsätter det tills textraderna når bildens nederkant. Då behöver de inte längre förkortas, utan texten fortsätter nedanför bilden.
En, två, tre, många
Låt oss se tillbaka på exemplet där vi lät flera element flyta åt samma håll. För varje flytande element förkortas våra line boxes och till sist finns det inte rum för nästa flytande element.
Nu ser vi nästa viktiga egenskap hos ett flytande element: när det inte finns plats på raden skiftas det nedåt tills det får plats. Det här kan vara oerhört värdefullt i layouter som ska fungera med olika fönsterstorlekar. Om spalterna är flytande läggs de sida vid sida i ett brett fönster, eller i vertikal sekvens i ett smalt fönster.
Öh du, den hänger utanför…
Som vi nyss såg kan ett flytande element fortsätta utanför det blockelement där det skapades. Ett blockelement förlängs inte så att det omsluter sina flytande barnelement, eftersom det skulle omöjliggöra den tjusiga effekt vi ville åstadkomma i exemplet ovan med den höga smala bilden. (Ett flytande element förlängs dock vid behov så att det alltid omsluter sina flytande barn.)
Ibland är den här effekten önskvärd, ibland inte. Lyckligtvis finns det ett
sätt att upphäva den. Vi kan tvinga ett element att lägga sig nedanför alla
flytande element via egenskapen clear
. Om vi sätter
clear:left
hamnar elementet nedanför alla flytande
element på dess vänstra sida. Med clear:both
kan vi
se till att ett element hamnar nedanför alla flytande element som annars skulle
ha påverkat det.
För ett blockelement åstadkoms förflyttningen genom att dess toppmarginal vid behov ökas tills dess att elementets överkant hamnar nedanför den nedre kanten på alla flytande element som skapats tidigare i dokumentet. (Känn dig inte skamsen om du måste läsa föregående mening ett antal gånger innan du förstår den, det här är klurigt.)
För inline-element är det ännu mer komplicerat, men
resultatet blir detsamma. Sen kan ett flytande element ha egenskapen
clear
också…
Varför behöver vi veta det här? Därför att det får konsekvenser som annars är
rätt obegripliga. Låt säga att vi vill vara säkra på att en
H2
-rubrik inte hamnar vid sidan av ett flytande element. Vi sätter
alltså clear:both
. Anta också att vi vill ha en viss
marginal mellan det flytande elementet och själva rubriken. Så vi sätter
margin-top:1em
. Risken är stor att vi hamnar i ett
läge där rubriken är alldeles för närgången mot det flytande elementet.
Varför?
Kom ihåg att flytande element inte påverkar efterföljande
block boxes. Om vi inte satt
clear
för rubriken skulle den ha skapats
1em
nedanför närmast föregående blockelement (och rubriken skulle
ha hamnat till höger om ett vänsterflytande element som sticker utanför sitt
blockelement). I och med att vi sätter clear
ökas
toppmarginalen tills rubrikens överkant hamnar nedanför det flytande elementets
nederkant. Men inte mer!
Om vi vill ha luft runt ett flytande element måste vi sätta marginaler på det flytande elementet.
Flytande spalter
Hur kan vi då utnyttja float
för att skapa en
layout med spalter, och varför är det ibland bättre än absolut
positionering?
Om vi kapslar in varje spalt i ett DIV
-element med
float:left
kommer de att placeras sida vid sida,
precis som vi förväntar oss att spalter ska göra. Vill vi sedan ha en sidfot som
spänner över hela sidbredden, och som ska visas längst ned oavsett vilken av
spalterna som råkar vara längst, då kan vi bara sätta
clear:both
på den.
I en flytande layout vill vi ange bredden för de olika spalterna i procent. Ett bra tips är att inte ange procentsiffrorna så att summan blir exakt 100 % (exempelvis 60/40 eller 25/50/25). Minsta lilla avrundningsfel gör nämligen att den sista spalten halkar nedanför de andra. Använd hellre 60/39 eller 25/49/25, för att vara på den säkra sidan.
En helt flytande layout medför dock problem i sig. I moderna webbläsare (läs:
allt utom IE) kan vi ange en min-width
(förslagsvis i em
) för varje spalt. I ett smalt fönster kan då en
eller flera spalter flyttas ned under de andra, vilket kanske inte är så
estetiskt tilltalande, men det är många gånger mer användarvänligt än att behöva
skrolla i sidled.
Vi löser alltså sidfotsproblemet med flytande spalter, något som inte kan hanteras med absolut positionering. Däremot får vi ett annat problem som är lättare att lösa med absolut positionering: ordningsföljden. Det är ofta önskvärt att ha sidans huvudinnehåll så tidigt som möjligt i källkoden. Mindre väsentliga saker som navigering och annonser kan komma längre ned. Men med flytande element måste vi ange dem i rätt ordning. Vill vi ha en meny till vänster, annonser till höger och innehållet i mitten kan inte innehållet komma först. Eller…?
Det går, men det är inte helt trivialt. Säg att vi vill ha innehållet först,
därefter vänstermenyn och så annonserna på slutet. Låt oss säga att vi vill
använda proportionerna 25/50/25. Vi vill också ha en kantlinje mellan spalterna.
För att åstadkomma detta behöver vi ta till ett par extra
DIV
-element för att gruppera saker. Strukturen blir följande:
<div id="outer">
<div id="inner">
<div id="content">Innehåll</div>
<div id="menu">Meny</div>
</div>
<div id="sidebar">Annonser</div>
</div>
Den yttre behållaren innehåller alltså en inre behållare (till vänster) samt annonsspalten (till höger). CSS-reglerna ser ut så här:
#outer {
margin:0 25%;
border:1px solid #000;
border-width:0 1px;
}
Vänster- och högermarginalerna sätts till 25 % för att lämna utrymme för sidospalterna, och så sätter vi en tunn svart kantlinje på vardera sidan.
Den inre behållaren består av innehållsspalten (till höger) och menyn (till vänster). CSS-regler:
#inner {
float:left;
width:99%;
}
#content {
float:right;
width:99%;
}
#menu {
float:left;
width:50%;
margin-left:-49.9%;
}
Själva den inre behållaren flyter alltså till vänster och upptar i stort sett hela den yttre behållarens bredd – bortsett från marginalerna förstås. Innehållsspalten är flytande inuti den flytande inre behållaren, och utnyttjar i stort sett hela dess bredd.
Menyn flyter till vänster, så den hamnar jämsides med den flytande innehållsspalten. Dess bredd ska motsvara den yttre behållarens vänstermarginal, som är satt till 25 %. Men om vi anger bredden för menyn i procent så är det i procent av den inre behållarens bredd, inte av sidans bredd. Den inre behållarens bredd är (knappt) halva fönsterbredden (99 % av 100 % minus två marginaler om 25 %), så menyns bredd blir ungefär 25 % dividerat med 0,5, vilket blir 50 %. Puh!
Som om inte detta vore nog har menyn en negativ vänstermarginal som är nästan lika stor som dess bredd. Med betoning på nästan. Den negativa marginalen gör att menyn, som ju bara har 1 % av den inre behållarens bredd att existera i, flyttas utanför den inre behållaren och hamnar i den yttre behållarens vänstermarginal som vi ju skapat för just detta ändamål.
Varför sätter vi då inte vänstermarginalen till -50%
? Därför att
vi inte får flytta ut hela menyn utanför den inre behållaren. Gör vi
det kommer den inte längre att ingå
i den inre behållaren, och om
menyspalten då råkar bli längst kommer den att skriva över en eventuell
sidfot. Eftersom menyn med denna teknik faktiskt kommer att överlappa den inre
behållaren en liten aning bör vi se till att menyns underelement har en viss
högermarginal för att ge lite luft.
Annonsspaltens CSS-regler liknar menyns:
#sidebar {
float:right;
width:50%;
margin-right:-49.9%;
}
Annonsspalten ligger innehållsmässigt efter den inre behållaren, som flyter till vänster och upptar 99 % av mitten. I och med att vi gör annonsspalten flytande till höger och ger den en negativ högermarginal flyttar vi ut den i marginalen precis som vi gjorde med menyn. Bredden beräknas på samma sätt och på motsvarande sätt som för menyn bör vi se till att underelementen har en viss vänstermarginal för att inte hamna ovanpå kantlinjen.
Om vi nu provar det här så fungerar det inte alls. Varför? I och med att alla beståndsdelarna är flytande och därmed tas bort ur dokumentflödet har den yttre och den inre behållaren inget innehåll alls! För att råda bot på detta lägger vi till ett par utjämnande element som ser till att behållarna omsluter sina flytande barn:
<div id="outer">
<div id="inner">
<div id="content">Innehåll</div>
<div id="menu">Meny</div>
<div class="cleared"> </div>
</div>
<div id="sidebar">Annonser</div>
<div class="cleared"> </div>
</div>
CSS-reglerna för dessa element ser ut som följer:
.cleared {
clear:both;
line-height:0;
}
Vad clear:both
betyder är väl inget större
mysterium vid det här laget, men vad har line-height
med saken att göra? Vi vill inte att dessa utjämnare ska ta någon plats, men om
vi lämnar dem helt tomma kommer de inte att tas med alls i vissa webbläsare.
Därför får de innehålla ett
och så sätter vi radhöjden
till noll.
Buggar, buggar och ännu fler buggar
Om du orkat läsa ända hit inser du förmodligen vid det här laget att flytande element inte är helt triviala. Det är därför inte alltför förvånande att även webbläsarna har vissa problem med dem. I alla fall vissa webbläsare. Vi talar naturligtvis om Internet Explorer för Windows.
IE/Win har så många och komplicerade buggar när det gäller flytande element att vi inte kan gå in på dem här. Exemplet i den här artikeln fungerar exempelvis inte alls i IE/Win, men artikeln är tillräckligt lång utan att vi ger oss in i buggrättningsdjungeln.
Lyckligtvis finns det en enkel lösning som rättar till många av dessa buggar.
Vi såg tidigare att alla flytande element blir en
block box. Standarden säger dessutom att egenskapen
display
ska ignoreras för flytande element, såvida
den inte anges som none
. Om vi anger
display:inline
för ett flytande element försvinner en
del av buggarna i IE/Win som genom magi. Ingen vet riktigt varför.
IE/Win gör inte om elementet till en
inline box, men många av buggarna rättas till.
I övrigt överlåter vi med varm hand buggdetaljerna till den oumbärliga webbplatsen Position Is Everything.
10 december 2004 • Senast ändrad 11 december 2004 kl 18.18 • Permalänk • Webb • 0 kommentarer
Kommentarer är avstängda, på grund av för mycket spam.
Skyll inte på mig. Skyll på idioterna som förstör webben genom spam.