Osa 1: useampi laatikko vierekkäin

Aloitamme lyhyellä itseopiskelukurssilla: CSS:ssä on paljon pieniä nopeasti omaksuttavia ominaisuuksia, kuten taustavärin tai tekstin tyylien asettaminen. Nämä kannattaa opiskella itsekseen ihan vain kokeilemalla niiden vaikutusta elementtiin, jossa on useampi rivi tekstiä. Oppimisen avuksi ohessa on listaus näistä. Suora linkki vie Mozilla Developer Networkiin, W3C taas CSS2-standardin kuvaukseen.

Ei näitä heti tarvitse osata ulkoa. Olennaisempaa on olla tietoinen näiden olemassaolosta. Seuraavaksi vuorossa on todella tiivistetty tietopaketti CSS:n perustoiminnasta.

Laatikkomalli

  • Marginaali (margin)
  • Reunus (border)
  • Sisennys (padding)
  • Sisältö

Tämä on todella olennainen asia ymmärtää, koska laatikkomalli vaikuttaa aivan kaikkeen, mitä ruudulla näytetään. Tämä malli on käytössä tekstiriveissäkin: rivin voi ajatella olevan yllä näkyvä laatikko, joka on hyvin hyvin hyvin leveä ja sitten se on vain pilkottu osiin ja läntätty päällekkäin. Tosin tekstiriveissä marginaaleilla ja sisennyksellä on vaikutusta ainoastaan ”laatikkonsa” vasempaan ja oikeaan laitaan, toisin sanoen tekstin alkuun ja loppuun.

Sisältöalue (content)

Elementin sisällä olevat toiset elementit sijoittuvat sisältöalueelle. Lisäksi kannattaa huomioida, että oletuksena kun CSS:ssä elementille annetaan leveys, annetaan se nimenomaan sisältöalueelle. Tämä tarkoittaa sitä, että elementin leveys kasvaa kun sille annetaan sisennystä tai reunus. Tämä on yksi asia, joka usein hämmentää CSS:n kanssa ensimmäisiä kertoja värkkääviä. Tähän logiikkaan on kuitenkin nykyään mahdollista vaikuttaa box-sizing:lla.

Sisennys (padding)

Sisennys on elementin sisällön ympärille luotava tyhjä tila. Sille ei voi määritellä väriä tai mitään muuta kuin tilan. Sisennys ei voi olla negatiivinen.

Reunus (border)

Toisin kuin sisennykseen, reunukselle on mahdollista asettaa väri ja tyyli. Nykyisin reunukseen voi laittaa myös toistuvan kuvan. Reunus ei voi olla negatiivinen.

Marginaali (margin)

Marginaali on elementtien välinen tyhjä tila. Tästä johtuen sillä on erityislaatuinen tapa toimia: kun kahden eri elementin marginaalit kohtaavat, niin ainoastaan marginaaleista suurempaa noudatetaan. Eli jos meillä on elementti A, jolla on alamarginaalia 1em ja sen alapuolella elementti B, jolla on ylämarginaalia 2em, niin elementtien väliin tulee tilaa 2em.

Marginaali voi myös olla negatiivinen ja sitä voi käyttää kasvattamaan elementin kokoa ulos sille muutoin saatavilla olevasta tilasta.

Ulkolinja (outline)

Yllä mainittujen lisäksi löytyy vielä yksi reunus: ulkolinja ei vaikuta muihin elementteihin ja se asettuu kaikkien yllä mainittujen elementin osien ulkopuolelle. Sitä ei kovinkaan usein käytännössä tarvita. Useimmiten ulkolinjaa käytetään ilmaisemaan, mikäli elementillä on fookus (esim. linkkien ympärille ilmestyvä pisteytetty tai värillä hohtava reunus).

Lohkot ja rivit

Kaikilla elementeillä on olemassa asetettu oletustyyli. Selainten välillä on jonkin verran eroavaisuuksia, mutta pääsääntöisesti nämä tyylit ovat samoja. Nykyisin on suosittua käyttää tyylien normalisointia, joka saa kaikki selaimet tuottamaan mahdollisimman samankaltaisen lopputuloksen oletustyylien osalta.

Olennaista oletustyyleissä on se, että suurimmalla osalla elementeistä on joko display: block; tai display: inline; — toisin sanoen elementit ovat joko lohkoelementtejä tai rivielementtejä.

Tyypillisiä lohkoelementtejä ovat esimerkiksi otsikot, kappaleet, div sekä monet HTML5:n uudet semanttiset sivun rakennetta ryhmittelevät elementit kuten main, article, section, header, footer sekä aside. Lohkot asettuvat sivulle hyvin yksinkertaisella tavalla: ne vievät niin paljon tilaa vaakasuunnassa kuin tilaa on saatavilla ja muutoin korkeus määräytyy lohkon sisällön tarpeiden mukaan.

Tavanomaisia rivielementtejä taas ovat vaikkapa linkit, kuvat, tekstin korostukset sekä span. Rivielementit taas soljuvat tekstin mukana ja ne voivat mennä useammalle riville, leikkautuen useampaan osaan.

Lohkot vierekkäin, kiitos!

Edellisen perusteella syntyy varmasti kysymys: miten ne lohkot on mahdollista laittaa vierekkäin? Tällä kysymyksellä on CSS-taiton aikakaudella ollut pitkään vain yksi vastaus: kellutus. Tilanne on kuitenkin viimein tällä vuosikymmennellä alkanut muuttua ja kellutuksen rinnalle on tullut kaksi uutta käyttökelpoista tapaa: taulukkotaitto ja display: inline-block;. Ja jos taulukkotaitto nostattaa niskakarvoja, koska siitä on kuullut paljon pahaa, niin sillä pahalla on tarkoitettu HTML-taulukkotaittoa, jossa HTML on kirjoitettu taulukoilla. Sillä ei ole tarkoitettu CSS:n display: table;:a.

Seuraavassa esimerkissä otamme tavoitteeksi asettaa kolme neliötä yhden isäntäelementin sisälle. Haluamme isäntäelementin yhä edelleen vievän kaiken saatavilla olevan tilan. Neliöt taas saavat olla tiukasti tasattuna vasemmassa reunassa. Isäntäelementin korkeuden tulee määräytyä sisältönsä mukaan, kuten normaalisti.

<!-- isäntäelementti -->
<div class="parent">
  <!-- kolme lapsielementtiä -->
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
</div>

Käytämme yllä näkyvää rakennetta esimerkeissä ensimmäistä lukuunottamatta. Rakenne luodaan diveillä, koska meillä ei ole sisältöä. Useimmiten on parempi käyttää rakennetta ja sisältöä kuvastavia elementtejä. Tämä seikka myös painottaa CSS:n vahvuutta: melkein minkä tahansa saa näyttämään melkein miltä tahansa (rajoituksia asettavat mm. lomake-elementit).

/* isäntäelementti */
.parent {
  background: #EC8;
}

/* lapsielementti */
.child {
  background: #FDA;
  border: 1px solid #B94;
  height: 5em;
  width: 5em;
}

/* ensimmäistä esimerkkiä varten asetamme korkeuden myös isäntään */
.parent--sample1 {
  height: 5em;
}

Yllä olevat parent- ja child-luokat ovat mukana kaikissa esimerkeissä. Näiden lisäksi kullakin esimerkillä on omat luokkansa, jotka laajentavat tyylejä. Nämä ovat tyyliä parent--sample# ja child--sample#, jossa # on esimerkin numero.

Esimerkki 1: Isäntäelementti ja lapsielementti

Isäntäelementille ei ole asetettu leveyttä, joten se vie kaiken saatavilla olevan tilan vaakasuuntaan.

Lapsielementille on asetettu leveys, mutta tämä teksti sijoittuu sen alapuolelle, koska kyseessä on kuitenkin lohkoelementti. Se ”varaa” yksin ollessaan aina kaiken tilan korkeudensa mitalta. Tämä tekstikin on tosin lohkoelementissä (p, eli paragraph, eli kappale). Tilanne ei kuitenkaan muuttuisi miksikään, vaikka teksti olisi sellaisenaan lähdekoodissa samalla tasolla kuin yllä oleva lohko.

Esimerkki 2: Kellutetut lapset isäntäelementissä

<div class="parent">
  <div class="child child--sample2"></div>
  <div class="child child--sample2"></div>
  <div class="child child--sample2"></div>
</div>

Mitäs himskattia nyt tapahtuu? Lapsielementit kyllä ovat ihan siellä missä pitää, mutta minne isäntäelementti on kadonnut ja minkä ihmeen vuoksi tämä teksti ilmestyy lapsielementtien oikealle puolelle? Tekstin jatko myös hypähtää yllättäen laatikkojen alapuolelle…

Katsotaanpa ensin esimerkkiin liittyvä CSS:

.child--sample2 {
  float: left;
}

Sitä ei paljon ole. Kellutukseen liittyvistä asioista tämä esimerkki kuitenkin kertoo paljon: elementit eivät enää vaikutakaan sivulla oleviin muihin elementteihin kokonsa puolesta. Näinpä isäntäelementille ei tule minkäänlaista korkeutta! Kellutetut elementit kuitenkin vaikuttavat siihen, missä teksti kulkee: teksti huomioi kellutetut elementit eikä tunkeudu kellutettujen elementtien alueelle. Kellutetut elementit myös huomioivat toisensa eivätkä asetu toistensa päälle.

Tähän kellutettujen elementtien vaikutusvalta sitten päättyykin yhtä seikkaa lukuunottamatta: CSS:stä löytyy myös mahdollisuus sijoittaa elementti kaiken kellutetun sisällön jälkeen. Tämä ominaisuus on clear: both; (arvo voi myös olla left tai right, aivan kuten floatissa). Isäntäelementtiämme tämä ominaisuus ei kuitenkaan auta! Se kun ei voi asettua sisällään olevien kellutusten jälkeen. CSS:stä löytyy kuitenkin mahdollisuus määrittää elementin ensimmäiseksi tai viimeiseksi lapseksi pseudoelementti!

Esimerkki 3: Kellutetut lapset clearfixatussa isäntäelementissä

<div class="parent parent--sample3">
  <div class="child child--sample3"></div>
  <div class="child child--sample3"></div>
  <div class="child child--sample3"></div>
</div>
/* tämä tunnetaan nimellä "micro clearfix" */
.parent--sample3:before,
.parent--sample3:after {
  content: ' ';
  display: table;
}

.parent--sample3:after {
  clear: both;
}

/* tämä on sama kuin esimerkissä 2 */
.child--sample3 {
  float: left;
}

Jippii! Nyt lopputulos on juuri sellainen kuin sen tuleekin olla! Isäntäelementti kasvaa sisältönsä mittaan kiitos pseudoelementin, joka asettuu kellutettujen elementtien alapuolelle. Pseudoelementillä ei ole korkeutta, joten sitä ei voi nähdä, mutta näemme sen olemassaolon vaikutuksen.

Harjoite: miten saat pseudoelementin näkyville? (Avaa esimerkki Codepenissä)

Nyt olemme saaneet kellutukset taitettua tahtomme alle (sanaleikki!), joten seuraavaksi voimme yrittää samaa lopputulosta taulukkotaitolla.

Esimerkki 4: table-cell -lapset table-isäntäelementissä

CSS-taulukkoja ei ole aiemmin käytetty kovinkaan paljon. Tämä on johtunut siitä, että Internet Explorer 6:n ja 7:n käyttäjämäärät ovat olleet riittävän merkittäviä. Tilanne kuitenkin muuttui huomattavasti noin vuoden 2012 tietämillä ja nykyisellään IE8 on monilla sivustoilla vähimmäisvaatimus. Ja hyväksi onneksi IE8 myös tukee CSS-taulukoita!

<div class="parent parent--sample4">
  <div class="child child--sample4"></div>
  <div class="child child--sample4"></div>
  <div class="child child--sample4"></div>
</div>
.parent--sample4 {
  display: table;
}

.child--sample4 {
  display: table-cell;
}

Hei, sehän on melkein oikein! Mutta… missä isäntäelementti luuraa?

Taulukot toimivat hyvin erilaisella logiikalla. Ne eivät oletuksena varaa kaikkea saatavilla olevaa tilaa vaakasuunnassa, vaan niiden koko on täysin sidoksissa sisältöönsä. Lapsielementit ovat juuri asetetun kokoisia, mutta jästipäinä toki haluamme isäntäelementin näkyville!

Esimerkki 5: table-cell -lapset 100% levyisessä table-isäntäelementissä

HTML on pysynyt samana, mutta luokissa on --sample5.

.parent--sample5 {
  display: table;
  width: 100%;
}

.child--sample5 {
  display: table-cell;
}

No ei nyt ihan mennyt oikein. Taulukot ovat varsin jänniä siinä mielessä, että ne ovat hyvin joustavia: taulukoiden solut ovat ennemminkin suhteessa toisiinsa kuin absoluuttisesti juuri sen mittaisia kuin asetamme. Näinpä kun pakotamme taulukon 100% leveyteen aiheutamme samalla sen, että lapsisolut kasvavat mitaltaan riittävästi, jotta koko 100% taulukon leveys tulee katettua. Mikäli soluilla olisi erilaisia arvoja, niin nämä toimisivat suhteellisina arvoina toisiinsa.

Taulukoissa joutuu huomioimaan myös sen seikan, että ne voivat kasvaa ikuisesti sisältönsä mukaan. Eli vaikka taulukon leveys on 100%, niin todellisuudessa se on vain minimileveys: jos solujen sisältö niin sanelee, niin taulukon koko voi kasvaa tuon 100% ylitse.

Mutta mutta. Onko mitenkään mahdollista, että saamme isäntäelementin näkyville?

Esimerkki 6: table-cell -lapset 100% levyisessä fixed table -isäntäelementissä, jolla on pseudolapsi

Oi kun ihana otsikko. HTML on edelleen pysynyt samana, mitä nyt luokat ovat numerolla 6.

.parent--sample6 {
  display: table;
  table-layout: fixed;
  width: 100%;
}

.parent--sample6:after {
  content: '';
  display: table-cell;
  width: 100%;
}

.child--sample6 {
  display: table-cell;
}

Nyt se näyttää siltä miltä pitääkin, mutta miksi näin on? Ensinnäkin olennainen lisäys on 100% levyisen table-cell -pseudoelementin käyttäminen: se vie  saatavilla olevan lopun tilan ja muutoin tyylittömänä se on ”näkymätön”, jolloin isäntäelementti pääsee näkyville.

Mainitsin kuitenkin aiemmin taulukoista, että ne voivat kasvaa loputtomiin. Ilman table-layout: fixed;:iä tämä esimerkki ei siten toimisikaan halutulla tavalla, vaan 100% pseudoelementtimme söisi aivan kaiken saatavilla olevan tilan, koska sen suhdeluku olisi tavallaan ääretön verrattuna kiinteällä leveydellä varustettuihin ja sisällöttömiin lapsisoluihin. Siispä lapsielementeistämme ei näkyisikään muuta kuin reunukset!

Kiinteät taulukot (fixed table) taas käyttävät erilaista standardissa määriteltyä laskentatapaa, joka on käsittääkseni myös yksinkertaisempi. Sen pohjalla ei ole solujen sisällön ääretön ylivalta taulukon mittoihin, vaan annetut mitat saavat isomman vastuun. Tällä tavoin kiinteällä leveydellä varustetut solut saavat pitää leveytensä. Soluihin määritellyt prosentuaaliset luvut ovatkin suhteellisia enää toisiinsa sekä jäljellä olevaan saatavilla olevaan tilaan. Täten 100% pseudolapsessa tarkoittaa kaikkea jäljellä olevaa tilaa. Koko taulukon 100% leveys kuitenkin on edelleen vain minimileveys.

Harjoite: miten saat keskitettyä lapsielementit? (Avaa esimerkki Codepenissä)

Seuraavaksi tutustumme rivilohkoelementtien maailmaan.

Esimerkki 7: inline-block -lapset isäntäelementissä

HTML ei ole muuttunut miksikään, mutta nollataan tilanne:

<div class="parent parent--sample7">
  <div class="child child--sample7"></div>
  <div class="child child--sample7"></div>
  <div class="child child--sample7"></div>
</div>
.child--sample7 {
  display: inline-block;
}

Oho, vaihteeksi saamme suorilta täytettyä melkein kaikki kriteerit:

  • Isäntäelementti ottaa huomioon lasten koon
  • Lapset ovat vasemmassa laidassa vierekkäin
  • … mutta niiden välissä on ylimääräistä tilaa, samoin kuin alalaidassa

Mistä ihmeestä tuo ylimääräinen tila syntyy? Ajattelumallia tarvitsee hienosäätää jälleen hyvin erilaiseen logiikkaan. Rivilohkot (millä nimellä olen itse alkanut inline-blockeja kutsua) ovat lähtökohtaisesti tekstiä, joten niiden rooli on olla kuin sanoja tai merkkejä. Tästä johtuen ne myös noudattavat tekstiä koskevia erikoissääntöjä.

Keskitytään ensin alareunan tyhjään tilaan. Tämä tila syntyy yksinkertaisesti siitä syystä, että kaikkien elementtien oletustapa asetella rivisisältöä on baseline. Tämä tarkoittaa kirjainten alareunaa. Olet kuitenkin ehkä joskus tullut kiinnittäneeksi huomiota, että jotkut kirjaimet kuten vaikkapa p ja j jatkuvat useampia kirjaimia alemmas. Näiden kirjainten alaosa menee baselinen alapuolelle. Katsotaan siis samaa esimerkkiä niin, että lisätään divien väliin pari sanaa:

poppamies

jakaa tietoa

Galaksit räjähtävät! Sehän jopa toimii loogisen näköisesti! Rivilohkoelementti tosiaan toimii kuin mikä tahansa merkki ja asettuu baselinen päälle. Mutta onko meillä keinoa hallita rivillä olevan tekstin pystysuuntaista sijoittumista?

Esimerkki 8: yläreunaan asetellut inline-block -lapset isäntäelementissä

.child--sample8 {
  display: inline-block;
  vertical-align: top;
}

poppamies

jakaa tietoa

yhä edelleen!

Sehän tapahtui nätisti! Teksti onkin yläreunassa, koska rivilohkot asettuvat nyt tekstin yläreunaan.

Nyt ongelmana onkin enää lohkojen väliin jäävä tila.

Esimerkki 9: yläreunaan asetellut inline-block -lapset nollafonttikoon isäntäelementissä

Esimerkissä 7 rivilohkojen välissä johtunut tila johtuu välilyönneistä. HTML:n elementit kirjoitetaan usein sisennettynä ja kukin alkaen omalta riviltään, jolloin elementtien väliin jää rivinvaihtoja ja välilyöntejä. Kahden lohkoelementin välissä näitä ei käytännössä huomioida, mutta rivilohkot käyttäytyvät kuten merkit, joten niiden väliin syntyy välilyönti.

Tätä ongelmaa ei ole suoraviivaista korjata ilman haittoja. Välilyöntien olemassaolon voi koettaa poistaa mm. asettamalla negatiivisen marginaalin, letter-spacingin tai word-spacingin, mutta meillä ei ole keinoa tietää välilyönnin todellista leveyttä. Väli ei myöskään välttämättä ole aina täsmälleen tietyn pikselimäärän mukainen. Tämä tarkoittaa sitä, että fonttia vaihtamalla tai fontin kokoa vaihtamalla leiska menee rikki ja tulee iso suruhymiö.

Luotettavimmaksi tavaksi olen täten todennut fonttikoon asettamisen nollaksi. Tällä taas on ollut sellainen haittapuoli, että tietyt selaimet (mm. entiset Presto-pohjaiset Operat sekä monet mobiili-Androidien selaimet) eivät olekaan toimineet tämän erikoisen ratkaisun kanssa järkevästi. Opera käyttää onneksi nykyään WebKitiä, joka on sama moottori kuin mitä Chrome ja Safari käyttävät, joten siinä on yksi ongelma vähemmän. Android-selainten ongelmana taas on ollut se, että niillä on absoluuttinen minimikoko fontille, joka on riittävästi aiheuttamaan 1 pikselin välejä ja sitten inline-blockiin pohjautuva leiska revähtääkin rikki.

Mutta miten on, toimiiko nollakoko?

.parent--sample9 {
  font-size: 0;
}

.child--sample9 {
  display: inline-block;
  font-size: 16px;
  font-size: 1rem;
  vertical-align: top;
}

Kyllähän se toimii! Koska fontin koko on 0, ei välilyönneillä ole kokoa. Näin laatikot asettuvat nätisti täysin vierekkäin.

Toinen seikka joka kannattaa huomioida on fontin asettaminen. Nollakoko katkaisee fonttikoon periytyvyyden, joten yleinen tapa asettaa fontin koko käyttäen em-yksiköitä ei enää toimikaan, koska voit heittää vaikka 100000em ja koko on yhä edelleen 0. Ongelman voi kiertää käyttämällä rem-yksikköä, joka on dokumentin pohjan em-yksikkö. Valitettavasti rem ei ole vielä kaikkien selainten tukema, selkeimpänä puutteena IE8. Tämän takia asetamme kaksi fonttikokoa: ensin fallback-tyylinä 16px, joka on modernien selainten oletuskoko fonteille. Sitten ylikirjoitamme koon käyttämällä 1rem, jolloin sitä tukevat selaimet käyttävätkin sitä.

Vinkki: SCSS on mainio työkalu hoitamaan remiin liittyviä ongelmia!

Viimeisessä esimerkissä teemme katsauksen Flexible Box Modeliin.

Esimerkki 10: lapsielementit flex-isäntäelementissä

Flex, flexbox tai flexible box, millä nimellä sitä haluaakaan kutsua, on vielä tätä kirjoittaessa tulevaisuutta. Sitä voi käyttää sivuilla, joilla ei ole tärkeää tukea vanhoja selaimia (kuten IE8 ja IE9). Useimmilla kaupallisilla sivustoilla vanhojen selainten käyttäjämäärät ovat vielä liian huomattavia. Omilla projektisivuilla sitä on kuitenkin mahdollista käyttää.

.parent--sample10 {
  display: flex;
}

PUTUM! Ihme on tapahtunut! Flex toimii suorilta juuri kuin tämän esimerkin kohdalla haluammekin: lapsielementit noudattavat niille asetettuja mittoja ja ne asettuvat vaakasuuntaan isäntäelementtiin, vaikka ne ovat lohkoelementtejä. Kannattaa huomioida, että muutamme isäntäelementin toimintaa toisin kuin kellutuksissa ja rivilohkoissa. Ja toisaalta meidän ei tarvitse tehdä lapsille mitään, toisin kuin taulukon kanssa toimiessa.

Selainyhteensopivuudesta

Nämä esimerkit toimivat sutjakkaasti ainakin seuraavilla selaimilla:

  • Firefox
  • Chrome ja Chromium
  • Opera 13 ja myöhemmät (WebKit-pohjaiset)
  • Internet Explorer 11
  • Internet Explorer 10 (vanha vuoden 2011 flexbox-syntaksi)
  • Internet Explorer 9 (ei flex-tukea)
  • Internet Explorer 8 (ei flex-tukea)
  • iOS Safari 7 ja myöhemmät
  • OSX Safari 6.1 ja myöhemmät
  • OSX Safari 6.0 ja 5.1 (ei flex-tukea, mutta tukevat vanhaa vuoden 2009 flex-standardia)

Operan vanha Presto ei osaa tukea fixed-taulukoita oikein, joten Opera 12.16 ja vanhemmat eivät näytä tämän sivun taulukoita kovinkaan hyvin.

Ainakin vanhemmissa Android-selaimissa taas on inline-blockien kanssa käytetyn nollafonttikoon kanssa ongelmia.

Tavoitteet tämän sivun luettuasi

  • Päähäsi on jäänyt muutamia värien ja tekstin tyylittelyyn liittyviä CSS:n ominaisuuksia.
  • Ymmärrät CSS:n laatikkomallin vaikutuksen rivielementeissä ja lohkoelementeissä.
  • Ymmärrät miten taulukkojen käyttäytyminen poikkeaa lohkoista.
  • Tiedät mitä haittapuolia on kellutuksen, taulukoiden ja rivilohkoelementtien käytössä taittoon.
  • Ja sen ansiosta tajuat, miksi flex on niin siisti juttu.
  • Saatat myös hoksata, miksi luokkien nimet on tällä sivulla nimetty kuten ovat.