CSS rychle a efektivně

Taky pořád čtete, jak optimalizovat CSS tím, že je spojíte do jednoho velkého souboru, který načtete v hlavičce HTML stránky?

No, Google si teď uvědomil, že to není tak úplně nejlepší a snaží se Chrome (a tedy i Operu a Safari) přepsat tak, aby se – světe div se – choval stejně jako Internet Explorer. Vývojáři Microsoftu totiž do IE (ať už záměrně nebo nechtěně) zakomponovali mechanismus, který umožňuje načítat CSS efektivněji po částech.

V čem je háček?

Dopravní zácpa vs. CSS
Stahovat CSS jednotlivě je jako jet do práce auty

Představte si načítání CSS jako cestu do práce. Pokud všechny soubory nalinkujete v hlavičce, je to jako by všichni lidé sedli ráno v 7 hodin do aut a vyrazili do práce. Pak se samozřejmě všechny ulice ucpou nikdo se nikam nedostane dřív než po osmé (v případě HTML – dřív než se všechny soubory nestáhnou do prohlížeče).

Tohle řeší hromadná doprava, kdy se všichni lidé nacpou do malého autobusu (tramvaje, metra, vlaku, apod.) a jedou společně.

Hromadná doprava vs. CSS
Stahovat CSS najednou je jako jet do práce autobusem

Výhoda je v tom, že ulice nejsou tak ucpané, ale každý jednotlivý člověk musí čekat, až ostatní nastoupí  a vystoupí tam, kde zrovna bydlí nebo pracují. A navíc, pokud vám autobus ujede, musíte čekat několik (desítek) minut na další. Cesta je tedy celkově rychlejší, ale pořád ne tak rychlá a pohodlná, jako kdyby jel člověk sám prázdnou ulicí přesně od svého domu ke své práci.

A stejně je to i s hromadným načítáním CSS. Sice se všechna data stáhnout rychle, protože prohlížeč nemusí neustále otevírat nová spojení pro každý soubor, ale zase musí čekat, až se stáhnou všechny definice včetně těch, které zrovna nepotřebuje (např. pro skryté prvky jako je vysouvací menu, nebo pro komponenty, které na současné stránce ani nejsou). Druhou nevýhodou je, že pokud se změní jedna jediná definice, je potřeba znovu stáhnout celý soubor.

Při cestě do práce, pokud nutně potřebujete jet autem, platí jednoduché pravidlo: pokud jsou ulice ucpané mezi 7. a 8. hodinou, je potřeba vyrazit před půl sedmou nebo až po osmé.

A stejně to můžete udělat i s CSS soubory. Tedy v hlavičce načíst jen to nejnutnější a zbytek načítat až postupně společně s tím, jak se budou načítat jednotlivé části (komponenty) stránky. Když prohlížeč najde na stránce článek, stáhne si k němu styly pro zobrazení článku, když najde obrázek, stáhne styly pro uspořádání obrázků, atd. až nakonec najde menu a patičku na konci stránky a stáhne pro ně příslušné styly (a nebo také ne, protože ví, že nejsou vidět – to ale budete muset ošetřit přes JS).

Jak na komponentní CSS

Systém načítání spočívá v tom, že před tím, než do HTML vypíšete určitou část stránky, vložíte kód pro načtení stylu (link). Prohlížeč, když najde odkaz na styl, pozastaví vykreslování stránky a počká, dokud se soubor nestáhne a pak teprve pokračuje ve vykreslování:

<html><head>
    <link rel=stylesheet href="main.css">
    <!-- main.css obsahuje layout stránky
     a hlavičky pro rychlé zobrazení -->
</head>
<body>
    <div class="header">...</div>

    <link rel=stylesheet href="article.css">
    <div class="article">...</div>
 
    <link rel=stylesheet href="footer.css">
    <div class="footer">...</div>
</body></html>

Firefox má v tomhle trochu problém, protože ve skutečnosti nepozastaví vykreslování stránky, pokud najde link v těle stránky a tak může dojít k vykreslení článku a patičky před tím, než bude k dispozici příslušný styl. Ošetřit se to dá tím, že do stránky přidáme (prázdný) skript, který vykreslování skutečně zastaví a počká na stažení stylů (protože neví, jestli skript nebude pracovat s definicemi stylů):

...
    <link rel=stylesheet href="article.css">
    <script> </script>
    <div class="article">...</div>

    <link rel=stylesheet href="footer.css">
    <script> </script>
    <div class="footer">...</div>
...

Skript může být prázdný, ale ne úplně prázdný, protože by ho Firefox ignoroval (protože je mu jasné, že nic dělat nemůže). Skript s mezerou je tedy dobrý kompromis, kdy si prohlížeč neví, co přesně bude dělat, ale ve skutečnosti nic nedělá a tedy nezdržuje víc, než musí.

A zde také právě narážíme na rozdíl mezi IE a Webkitem (Chrome, Opera, Safari). Internet Explorer totiž v okamžiku, kdy čeká na stažení CSS souboru, může vykreslovat tu část stránky, kterou již má staženou. Tedy v okamžiku, kdy stahuje footer.css, může renderovat článek, protože CSS i HTML kód pro article už má k dispozici. Stejně se chová i Firefox v případě, že použijete trik s prázdných skriptem.

Naproti tomu Webkit při stahování stylů pozastaví nejen zpracování následného HTML kódu, ale také zastaví renderování předešlého kódu, které má ale již plně k dispozici. To má celkem logické vysvětlení, protože právě stahovaný soubor může (čistě teoreticky) obsahovat definice, které ovlivní již načtený kód. A toto se právě vývojáři Googlu snaží změnit, aby i Webkit mohl renderovat již zpracovaný HTML kód v okamžiku, kdy stahuje soubor stylů. Tato změna je momentálně (únor 2016) ve vývojové (Canary) verzi a mohli bychom se jí dočkat přibližně v Chrome 50+.

Využívání komponent

Tento systém načítání komponent je i příhodný pro frameworky, které generují HTML kód z komponent na serveru. Každá komponenta tak může mít vlastní CSS soubor, který si sama vloží do stránky:

<?php
class HtmlComponent {
    protected static $css;
    protected static cssLoaded;

    protected function loadCss() {
        if (!static::$cssLoaded) {
            return '<link
               rel=stylesheet href="',
               static::$css, '">
               <script> </script>';
            static::$cssLoaded = true;
        }
        return ''; //no CSS, already loaded
     }
}

class Article extends HtmlComponent {
    protected static $css = 'article';
    protected static cssLoaded = false;
    protected $data;
    public function __constructor($data) {
        $this->data = $data;
    }
    public function __toString() {
        $s = $this->loadCss();
        $s .= '<div class="article">';
        //... zpracuj $this->data
        $s .= '</div>';
        return $s;
    }
}

//výpis stránky
foreach ($articles as $article) {
    echo new Article($article);
}

Podobně můžete od HtmlComponent odvodit i ostatní části stránky jako je menu, galerie, patička, atd. a každá komponenta si sama stáhne příslušné styly, jen pokud je bude potřebovat.

Poznámka pro PHP: Všimněte si, že funkce loadCss() používá static::$css místo obvyklého self::$css, což je tzv. Late Static Binding dostupné od PHP 5.3.0, které zajistí, že každá komponenta bude používat vlastní statickou proměnnou místo jedné společné HtmlComponent::$ccs. Ostatní jazyky tohle budou muset řešit po svém aby zajistili, že každá komponenta bude používat vlastní proměnné css a cssLoaded.

Omezení stylů na komponentu

Aby styly ve staženém souboru platili skutečně jen na danou komponentu, musíte je samozřejmě omezit pomocí selektoru v definici pravidel. Nejjednodušším způsobem je přidat typ komponenty do cesty selektoru:

.article { ... }
.article p { ... }
.article a { ... }
.article img { ... }

Tento způsob ale není příliš efektivní, protože prohlížeč musí u každého pravidla ověřit cestu a navíc bude pravidla zkoušet aplikovat i na stejné prvky v jiných komponentách. Pokud se ale týká jen pár pravidel, jako v předchozím příkladu, dá se to snést. Pokud navíc víte, že daná komponenta bude mít jen pár úrovní zanoření prvků, můžete použít označení pro přímého potomka a tím omezit pravidla jen do dané určité stránky:

.article { ... }
.article > p { ... }
.article > a,
.article > p > a { ... }
.article > p > img,
.article > p > a > img { ... }

Samozřejmě nejefektivnější a nejrychlejší by bylo, aby každý prvek uvnitř komponenty měl vlastní třídu, která by označovala jeho typ a příslušnost ke komponentě:

.article { ... }
.article-paragraph { ... }
.article-link { ... }
.article-image { ... }

V tomto případě jsou všechna pravidla omezena na danou komponentu a zároveň nezpůsobují prohlížeči problém s hledáním pravidel pro jednotlivé elementy. Pak by ale bylo příhodné – pokud generujete HTML na serveru z komponent nebo tříd – vytvořit sub-komponenty pro jednotlivé prvky a vytvářet je pomocí nich:

class Article extends HtmlComponent {
    protected static $css = 'article';
    //...
    public function __toString() {
        $s = $this->loadCss();
        $s .= '<div class="article">';
        $p = new HtmlParagraph(self::$css);
        $s .= $p->open();
        $s .= $this->data['text'];
        $s .= $p->close();
        $a = new HtmlLink(self::$css);
        $a->setLink($this->data['link']);
        $s .= ''.$a; //zavolá toString()
        $s .= '</div>';
        return $s;
    }
}

Každá sub-komponenta tak může sama zajistit, aby její definice obsahovala správně strukturované jméno třídy pro označení jejího typu a příslušnosti ke komponentě, jejíž jméno jí dáte v konstruktoru.

Podpora

Jak už bylo řečeno, Internet Explorer (IE9+) a Firefox již tento systém načítání CSS podporují. Chrome a Opera ho bude podporovat v řádu měsíců (odhadem 2. čtvrtina roku 2016) a Safari (jak je zvykem) se přidá někdy během roku (při vydání další verze iOS).

Důležité je, že starší verze Webkitu sice tento systém nevyužijí naplno, protože budou styly stahovat o něco pomaleji (kvůli rozdělení na řadu requestů), ale nebudou muset stahovat definice pro komponenty, které na stránce nejsou, a navíc budou profitovat z možnosti cachovat menší kusy a tudíž nebudou muset znovu stahovat styly komponent, které se od poslední návštěvy nezměnily.

A vzhledem k rychlosti rozšíření nových verzí webkitu do světa můžeme očekávat plnou podporu u více než 90% uživatelů počátkem roku 2017.

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *