SVG s fallbackem na PNG

Pro responzivní stránky je nejlepší použít vektorové obrázky SVG. Stále ale ještě existují prohlížeče, které ho nepodporují a tak je potřeba zajistit, aby se zobrazilo alespoň PNG (i když ve špatné kvalitě).

Update: uběhlo pár let a všechny momentálně používané prohlížeče již podporují SVG v použitelné podobě. Pokud již nepotřebujete podporovat IE8 (a starší), Android 1 a 2 a iOS 1, 2 a 3 (a ostatní podobně staré systémy), můžete se na PNG vykašlat a používat už jen SVG obrázky. Jen si dejte pozor na Android 3 a 4, kde nefungují některé SVG funkce (např. maskování) a IE9+ a Edge, které nemusejí správně zachovat poměr stran při zvětšování.

SVG v HTML

Pro vložení SVG v HTML můžete použít přímo tag SVG, který díky svému zápisu automaticky vytvoří fallback (zpětnou kompatibilitu) pro zobrazení PNG obrázku:

<svg width="64px" height="64px">
   <image 
    xlink:href="image.svg"
    src="image.png"
    width="100%" height="100%" />
</svg>

Prohlížeč, který zná SVG formát (který se skládá z dvojice tagů SVG-IMAGE nebo SVG-USE), použije definici z xlink:href a stáhne vektorový obrázek. Starší prohlížeče tag SVG ani odkaz xlink nepochopí a tak stáhnou obrázek z atributu src (tag image pochopí jako alias pro používanější img). Některé starší verze IE s experimentální podporou SVG mohou stáhnout oba obrázky (SVG i PNG), což je ale daň za to, že uživatel používá starý prohlížeč.

U této metody je potřeba pamatovat, že DOM struktura bude obsahovat dva vnořené elementy (SVG a IMAGE). Musíte tedy správně přizpůsobit CSS a JS definice (XPATH).

Pro kompatibilitu se všemi prohlížeči (ano, správně se domníváte, že tím myslím IE) je potřeba zadat obrázku (myšleno SVG i IMAGE) nějaké rozměry, aby prohlížeče věděly, jak správně vykreslit (vypočítat) vektorovou grafiku. Rozměry vnitřního obrázku mohou být 100%, aby byl stejně velký jako jeho rodič. Tomu pak nastavte rozměr podle situace – buď procentuálně nebo absolutně. Požadovanou skutečnou velikost samozřejmě můžete určit v CSS (protože CSS má vyšší prioritu než atributy width a height). Nejlepší je tak v HTML nastavit rozměr třeba 64x64px a v CSS pak zadat skutečné rozměry (např. v EM jednotkách).

Zjednodušení přes srcset

Vlastnost srcset je nový atribut v HTML5, který umožňuje stahovat různé obrázky podle rozlišení obrazovky. Využít se dá i pro načtení SVG:

<img src="image.png" srcset="image.svg" />

Nový prohlížeč (Edge, Firefox, Chrome, atd.) zná atribut srcset a proto stáhne vektorový soubor. Naopak staré prohlížeče (momentálně všechny IE a staré mobily) srcset neznají a tak stáhnou PNG tak jako u obyčejného obrázku.

Tento přístup je lepší než předchozí ze dvou důvodů: 1) nezmění se DOM struktura, protože bude stále obsahovat pouze daný obrázek (v porovnání s dvojicí SVG-IMAGE v předchozím případě) a 2) vyřeší se problém s IE, které nesprávně SVG zobrazuje (protože ho vůbec nenačte). Nevýhodou je pak to, že pomocí srcset pokryjete mnohem menší procento prohlížečů (celosvětově 96% vs. 68% na počátku 2016), než kolik jich skutečně SVG podporuje (protože SVG podporují všechny prohlížeče od IE9+ a iOS4+, zatímco srcset podporují jen moderní Edge a iOS9+; Chromium a Firefox samozřejmě nepočítám a u Android je to 4.4 vs. 5+ takže celkem minimální rozdíl).

Nicméně pokud potřebujete do stávajících stránek rychle doplnit podporu responzivních obrázků a pokrýt alespoň uživatele s nejnovějšími prohlížeči, můžete použít tento „trik“ místo pracného nahrazování img za svg-image.

Chyby v IE a iOS8

V Internet Exploreru se na SVG aplikuje stejná chyba jako u ostatních obrázků, která spočívá v tom, že neumí správně spočítat výšku, pokud je zadána jako automatická (height: auto;). Pak musíte použít JavaScript a výšku SVG nastavit při každé změně velikosti stránky:

if (-1 < navigator.userAgent.indexOf('MSIE') 
 || -1 < navigator.userAgent.indexOf('Trident/')
 || -1 < navigator.userAgent.indexOf('Edge/')
) {
    $(window).on('resize.svg-size', 
        function() {
            $('svg').each(function() {
                var me = $(this); 
                //pro čtvercové SVG:
                me.height(me.width());
            });
         });
    $(function() {
        $(window).triggerHandler(
            'resize.svg-size'); 
    });
}

Na iPhone a iPad (iOS8 a starších) se provádí vykreslování SVG trochu nestandardně (a dalo by se říci i chybně). Safari (resp. WebView poháněné webkitem) totiž správně aplikuje rozměry zadané v CSS na vnější SVG element, ale vnitřní IMAGE element vyrenderuje ve velikosti podle zadaných HTML atributů width a height, přičemž zcela ignoruje skutečnou velikost elementu. Proto je potřeba velikost spočtenou z CSS uložit i do HTML atributů:

if (navigator.userAgent.match(
    /(iPhone|iPad|iPod)\ OS\ [1-8]_/)) {
    $(window).on('resize.svg-size', 
        function() {
            $('svg').each(function() {
                var me = $(this);
                me.attr('width', me.width());
                me.attr('height', me.height());
            });
    });
    $(function() {
        $(window).triggerHandler(
            'resize.svg-size');
    });
}

SVG v CSS

Pro použití SVG jako obrázku na pozadí můžete využít toho, že vlastnost background-image u novějších prohlížečů (které SVG podporují) umožňuje zadat více obrázků oddělených čárkou, přičemž jedna z validních hodnot je none.

Oproti tomu všechny prohlížeče, které nepodporují SVG, taktéž nepochopí, když do background-image uvedete none a tak budou danou definici ignorovat:

el {
    background-image: url(image.png);
    background-image: none, url(image.svg);
}

Druhý řádek v nové prohlížeči přepíše ten první, takže se bude stahovat jen SVG. Staré prohlížeče ale budou druhý řádek považovat za nevalidní, budou ho ignorovat a proto se stáhne PNG.

Alternativně lze místo hodnoty none použít definici barvy přes rgba() funkci, která není taktéž podporována na starých prohlížečích. Pozor ale na to, že v tomto případě musíte místo background-image použít celé background a znovu nadefinovat všechny vlastnosti:

el {
    background: transparent url(image.png) repeat center center;
    background: rgba(0,0,0,0) url(image.svg) repeat center center;
}

Ještě lepší metoda, která odstraňuje problém s některými starými telefony (konkrétně Android 2.x), je místo hodnoty none použít gradient, který na starých prohlížečích není podporován (a na nových nic nezmění, protože bude průhledný). Problém je, že aby fungoval na webkitu, musíte uvést ještě prefixovanou verzi:

el {
    background-image: url(image.png);
    background-image: -webkit-linear-gradient(transparent, transparent), url(image.svg);
    background-image: linear-gradient(transparent, transparent), url(image.svg);
}

SVG v HTML5

V HTML5 také existuje element PICTURE, který umožňuje zadat obrázek v různých formátech. Více viz Responzivní načítání obrázků.

<picture>
   <source type="image/svg+xml" src="picture.svg">
   <img src="picture.png">
</picture>

Optimalizace PNG

Pro vytvoření co nejmenšího PNG se podívejte na článek Úprava obrázků pro web.

Optimalizace SVG

Stejně jako PNG a JPG mohou i SVG obsahovat řadu zbytečných informací: 1) data, která si do souboru ukládá editor (např. Photoshop) proto, aby mohl po znovuotevření soubor lépe editovat (a pro zobrazení v prohlížeči jsou nepotřebné), 2) redundantní údaje, které stačí uvést jen jednou (např. atributy width/height a viewBox, které oba definují velikost obrázku), 3) neoptimalizované instrukce, které definují něco, co uživatel neuvidí (např. překrývání čar, opakující se PATH, zbytečně dlouhá desetinný čísla, atd.), 4) formátování XML pro čitelnost (mezery, tabulátory, odřádkování, atd.) a 5) svg styly, které některé editory definují (obdoba CSS v odděleném souboru nebo style) a které je lepší uvést inline přímo tam, kde jsou potřeba.

Všechno tohle můžete z SVG odstranit bez toho, aby to nějak ovlivnilo výsledný obrázek. Dejte pozor ale na to, abyste naopak neodstranili něco, co v souboru být musí a bez čeho prohlížeč nepozná, jak má SVG vykreslit. Takovými informacemi jsou např. XML hlavička (<?xml version="1.0" ?>), informace o typu (xmlns) či ukončovací tagy (není to HTML, takže všechny tagy musí být ukončeny). Dejte pozor na to, že každý prohlížeč používá jiný XML parser a renderer, takže to, že odstranění nějaké informace jednomu prohlížeči nevadí, neznamená, že se SVG vykreslí i ve všech ostatních.

Diskutabilní jsou desetinná čísla. Například uvést šířku čáry jako 1.1234567890123456789 (protože jste ji kreslili na tabletu a tohle byla síla přítlaku) je jistě zbytečné a 1.12 bude prakticky stejné (a ušetříte 16B), na druhou stranu pokud obrázek hodně zvětšujete, může chyba v zaokrouhlení způsobit například to, že na sebe čáry nebudou navazovat a bude mezi nimi mezera.

Pro jednorázovou optimalizaci SVG můžete použít webový nástroj SVG Optimizers‘ Missing GUI, který umí nejen odstranit nadbytečné informace (sami zvolíte, co se smaže), ale také dokáže překreslit čáry a cesty tak, aby jejich definice byla co nejkratší. Navíc vám výsledný obrázek ukáže, takže můžete ověřit, že se nezměnil výsledný vzhled. Pro profesionálnější vývoj pak můžete použít přímo SVG Optimizer, který vyžaduje Node.js a ovládání přes konzoly (ale jsou dostupné i pluginy pro Grunt, Gulp, Inkscape, apod.).

Ptáte se proč optimalizovat SVG, které má pár kilobajtů (v porovnání s JPG, které mohou mít i desítky megabajtů)? Protože každý byte se počítá a urychluje přenos. TCP packet může být maximálně 64kB, takže pokud máte např. složité SVG logo, které má 100kB, už to znamená, že se nevejde do jednoho packetu a bude jich potřeba poslat víc (což znamená nadbytečnou komunikaci mezi klientem a serverem). To ale není vše, fyzicky sítě přenášejí data po přibližně 1500 bajtech (tzv. datagram, viz MTU), takže větší pakety je potřeba rozdělit a přenášet postupně. To znamená, že na čím více datagramů se packet rozdělí, tím je větší šance, že se při doručení ztratí (zvláště při přenosu přes WiFi a mobilní síť) a bude ho potřeba přenést znovu.

Komprese SVG

Jelikož jsou SVG soubory interně uloženy jako XML, jde tedy o text a je možno je poměrně dobře komprimovat. Na webu se často používá komprese gzip, kterou podporuje většina prohlížečů.

Pokud váš server běží na Apache, můžete nechat SVG automaticky komprimovat tak, že do složky, kde jsou obrázky uloženy (např. /public/images/), přidáte soubor .htaccess a do něj vložíte:

# compress SVG images
<IfModule mod_filter.c>
    <ifmodule mod_deflate.c>
        AddOutputFilterByType DEFLATE image/svg+xml
    </ifmodule>
</ifmodule>

V konfiguraci Apache (https.conf) pak najděte následující řádky a smažte „#“ na jejich začátku (pokud tam je):

LoadModule filter_module modules/mod_filter.so
LoadModule deflate_module modules/mod_deflate.so

Pokud máte jiný server (IIS, nginx, apod.) může být konfigurace jiná.

Tip: ve Windows průzkumníku nelze vytvořit soubor začínající tečkou, ale lze ho vytvořit v konzoly. Ve Windows 10 stačí najít složku v průzkumníku, zvolit SouborOtevřít příkazový řádek a zadat:

copy nul .htaccess

Dopředu zabalené SVG

Alternativně, pokud komprese při každém requestu není vhodná nebo použitelná, existuje speciální formát pro komprimované SVG. Můžete ručně zabalit SVG pomocí gzip a uložit s příponou *.SVGZ.

Na Windows můžete pro kompresi použít program 7zip (nezapomeňte zvolit metodu gzip místo 7zip!), na linuxu si vystačíte s příkazem:

gzip -c logo.svg > logo.svgz

#pokud neni gzip nainstalovan:
yum install gzip
#nebo
apt-get install gzip

Poté, co získáte komprimovaný obrázek, je potřeba nastavit server tak, aby místo SVG souborů odesílal SVGZ těm prohlížečům, které podporují gzip kompresy (což se pozná podle HTTP hlavičky). Příklad podle dokumentace k Apache mod_deflate:

 #Musíte zapnout mod_headers.so v Apache
<IfModule mod_headers.c>
    #pokud prohlížeč podporuje gzip
    RewriteCond "%{HTTP:Accept-encoding}" "gzip"
    #a chce stáhnout svg soubor
    RewriteCond "%{REQUEST_FILENAME}\.svg" -s
    #pošli mu místo něj svgz
    RewriteRule "^(.*)\.svg" "$1\.svgz" [QSA]
    #nastav správný MIME type (a vypni deflate module)
    RewriteRule "\.svgz$" "-" [T=image/svg+xml,E=no-gzip:1]
    #a pošli hlavičky informující o kompresi obsahu
    <FilesMatch "\.svgz$">
      Header append Content-Encoding gzip
      Header append Vary Accept-Encoding
    </FilesMatch>
</IfModule>

 

Napsat komentář

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