Srážkový radar, který plyne: Leaflet, crossfade snímků a lazy-init
Radar je nejvíc „živý" prvek appky — a nejnáročnější na výkon i soukromí. Jak ho rozhýbat plynule, načíst až když ho uživatel uvidí, a vyřešit 3rd-party dlaždice.

Mapa a snímky: Leaflet jako základ
Srážkový radar je bezpochyby nejatraktivnějším a nejvíce dynamickým prvkem moderních meteorologických aplikací. Když jsem navrhoval celý projekt Weather InTouch, věděl jsem, že radar bude středobodem uživatelského rozhraní. Zároveň se však jedná o technicky nejnáročnější komponentu celé platformy. Implementovat srážkový radar do webu v Leafletu totiž znamená vyřešit hned několik protichůdných požadavků: extrémní nároky na výkon při vykreslování desítek snímků nad mapovým podkladem, vizuální blikání při přepínání snímků v časové smyčce a v neposlední řadě ochranu soukromí uživatelů, protože mapové dlaždice se standardně stahují ze serverů třetích stran.
Základem celého řešení je knihovna Leaflet. Na rozdíl od robustních a často placených platforem typu Google Maps nebo Mapbox GL JS nabízí Leaflet vynikající výkon na mobilních zařízeních, minimální velikost výsledného JavaScriptu a naprostou svobodu při volbě mapových podkladů.
Radar v naší aplikaci nepracuje s předpovědí ani modelovými daty. Zobrazuje výhradně nedávnou historii – tedy poslední pozorované snímky srážek zachycené meteorologickými radary v časové smyčce, která končí v přítomnosti. Tato data jsou nejčastěji poskytována jako dlaždice (tiles) nebo jako statické obrázky s přesně definovanými geografickými souřadnicemi rohů (image overlays).
Pro základní inicializaci mapy a přípravu pro radarové snímky stačí jednoduchá konfigurace:
const map = L.map('radar-map', {
center: [50.08, 14.43],
zoom: 6,
zoomControl: false,
attributionControl: true
});
const baseLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
});
Nad touto podkladovou vrstvou pak rotuje sada snímků reprezentující konkrétní časové okamžiky z nedávné minulosti, typicky v pětiminutových nebo desetiminutových intervalech.
Crossfade místo blikání
Pokud se pokusíte implementovat přehrávání radarových snímků prostým přepínáním viditelnosti jednotlivých vrstev v poli, narazíte na nepříjemný vizuální dopad. Prohlížeč nestíhá okamžitě vykreslit nově zobrazenou vrstvu, což se projevuje jako neustálé bílé nebo průhledné blikání mezi jednotlivými kroky animace. Na mobilních zařízeních s horším připojením nebo slabším procesorem se z plynulé animace stane trhaná prezentace, která působí lacině.
Řešením je technika crossfade – plynulý přechod pomocí změny průhlednosti (opacity) mezi snímky. Místo toho, aby se předchozí snímek okamžitě skryl, se jeho průhlednost postupně snižuje, zatímco u nového snímku se lineárně zvyšuje.
Při tomto přístupu máme v paměti načtených několik vrstev současně. Leaflet umožňuje nastavit průhlednost pro každou vrstvu pomocí metody setOpacity(). Klíčem k úspěchu je také přednačítání (preloading). Než se spustí samotná animace na další snímek, musíme se ujistit, že daný obrázek nebo dlaždice jsou již plně stažené v mezipaměti prohlížeče.
function crossfade(layerOut, layerIn, duration = 200) {
let start = null;
function step(timestamp) {
if (!start) start = timestamp;
const progress = Math.min((timestamp - start) / duration, 1);
layerOut.setOpacity(1 - progress);
layerIn.setOpacity(progress);
if (progress < 1) {
requestAnimationFrame(step);
} else {
layerOut.setOpacity(0);
}
}
requestAnimationFrame(step);
}
Využití requestAnimationFrame zaručuje, že přechody jsou dokonale plynulé a synchronizované s překreslovací frekvencí monitoru či displeje telefonu. Výsledkem je sametově hladký pohyb srážkových polí bez jakéhokoliv blikání.
Přehrávání: play, pauza, posuvník
Aby byl radar pro uživatele skutečně užitečným nástrojem, musí mít plnou kontrolu nad časovou smyčkou. Základní ovládací rozhraní se skládá ze tří hlavních prvků: tlačítka pro spuštění a pozastavení přehrávání (play/pause), časového posuvníku (timeline slider) a tlačítek pro krokování vpřed a vzad.
Stav přehrávače je řízen jednoduchým stavovým automatem. Když uživatel klikne na tlačítko play, spustí se časovač, který v pravidelných rozestupech (např. každých 800 milisekund) posouvá index aktivního snímku. Pokud uživatel začne manuálně hýbat s posuvníkem, přehrávání se okamžitě pozastaví, aby se předešlo konfliktům mezi automatickým posunem a manuálním výběrem času.
Při implementaci časového posuvníku (často realizovaného pomocí standardního HTML5 prvku <input type="range">) je nutné ošetřit události tak, aby se změna projevila okamžitě, ale zároveň nedocházelo k přetížení překreslovacího vlákna. Každému snímku odpovídá konkrétní čas pozorování, který se dynamicky zobrazuje v textovém štítku vedle ovládacích prvků. Krokovací tlačítka pak pouze inkrementují či dekrementují index pole načtených vrstev a okamžitě vyvolají plynulý přechod na sousední snímek.
Lazy-init: radar se nestaví, dokud není vidět
Srážkový radar je z hlediska přenesených dat i výpočetního výkonu nejnáročnější částí webu. Každý krok animace představuje samostatný obrázek nebo sadu dlaždic, které musí prohlížeč stáhnout. Pokud by se radar inicializoval hned při prvním načtení stránky, uživatelé, kteří se k němu na úvodní stránce ani nedoscrollují, by zbytečně stahovali megabajty dat. To by negativně ovlivnilo metriky Core Web Vitals, zejména Cumulative Layout Shift (CLS) a Largest Contentful Paint (LCP).
Proto je celá komponenta navržena s ohledem na línou inicializaci (lazy-init) pomocí rozhraní IntersectionObserver.
Dokud se kontejner s radarem nedostane do zorného pole (viewportu) uživatele, mapa se vůbec nevytváří a nestahují se žádné podkladové dlaždice ani radarové snímky. Na místě radaru se zobrazuje pouze lehký zástupný prvek (placeholder) s odpovídajícími rozměry, aby se předešlo posunům obsahu stránky.
const radarContainer = document.getElementById('radar-container');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
initializeRadar();
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '200px'
});
observer.observe(radarContainer);
Nastavením parametru rootMargin na hodnotu 200px zajistíme, že se mapa začne na pozadí připravovat těsně předtím, než k ní uživatel doscrolluje. Pro uživatele je pak přechod zcela neviditelný – když dorazí k radaru, mapa je již připravena k interakci.
Souhlas akcí místo dalšího banneru
V dnešní době je ochrana soukromí uživatelů naprostou prioritou. Načítání mapových podkladů od třetích stran však znamená, že prohlížeč uživatele odesílá požadavky na servery těchto služeb, čímž dochází ke sdílení IP adresy a dalších síťových identifikátorů. Podle legislativy GDPR by k tomuto přenosu nemělo dojít bez předchozího aktivního souhlasu uživatele.
Místo toho, abychom uživatele obtěžovali dalším vyskakovacím cookie bannerem, který by blokoval celou aplikaci, využívá se zde koncept „souhlasu akcí“ (consent-by-action).
Když uživatel doscrolluje k radaru, mapa se nenačte automaticky. Místo ní se zobrazí elegantní statická krycí vrstva s informací, že zobrazení interaktivní mapy vyžaduje stažení dat od externího poskytovatele. Součástí této vrstvy je jasné tlačítko pro aktivaci.
Teprve kliknutím na toto tlačítko vyjádří uživatel aktivní souhlas s načtením mapových podkladů. Tato volba se následně uloží do lokálního úložiště prohlížeče (localStorage), takže při příští návštěvě se již radar zobrazí automaticky bez dalšího klikání. Tento přístup výrazně zlepšuje uživatelský zážitek a zároveň striktně respektuje ochranu osobních údajů.
Legenda: z dBZ na mm/h
Aby byla data ze srážkového radaru pro běžného uživatele srozumitelná, nestačí mu předložit pouze pestrobarevné skvrny na mapě. Radarová měření se standardně provádějí v jednotkách radarové odrazivosti, které se označují jako dBZ (decibely odrazivosti). Pro běžného člověka je však hodnota "35 dBZ" naprosto neříkající. Uživatel potřebuje vědět, zda bude drobně mrholit, nebo se blíží silný liják.
V aplikaci je proto implementována legenda, která tyto technické hodnoty převádí na srozumitelné milimetry za hodinu (mm/h). K tomuto přepočtu se používá standardní meteorologický Marshall–Palmerův vztah:
Z = a · R^b
kde Z představuje radarovou odrazivost, R je intenzita srážek v mm/h a koeficienty a a b jsou empiricky stanovené konstanty. Pro běžné dešťové srážky v mírném pásmu se nejčastěji používají hodnoty a = 200 a b = 1,6.
Díky tomuto přepočtu můžeme na barevné škále legendy zobrazit reálné hodnoty:
- Světle modrá (cca 15–20 dBZ) odpovídá slabému mrholení (do 0,5 mm/h).
- Zelená až žlutá (30–40 dBZ) značí mírný až středně silný déšť (2–8 mm/h).
- Červená a fialová (nad 50 dBZ) varují před extrémními srážkami a krupobitím (desítky mm/h).
Součástí mapového rozhraní je také špendlík polohy (teardrop pin). Jde o sdílenou SVG komponentu, která je úzce propojená s location pickerem z hlavní vyhledávací lišty aplikace. Tento špendlík dynamicky ukazuje aktuálně vybranou polohu uživatele přímo na mapě radaru. Uživatel tak na první pohled vidí, jak blízko k jeho domu se srážkové pásmo nachází, což dramaticky zvyšuje praktickou hodnotu celé vizualizace.
Pokud vás zajímá, jak je platforma poskládaná z hlediska celkové architektury, kde kombinuji technologie NestJS, MongoDB a Redis pro zajištění rychlé odezvy, doporučuji nahlédnout do mého předchozího článku. Podrobné informace o funkcích, designu a celkovém směřování tohoto projektu pak naleznete přímo na stránce popisující celý projekt Weather InTouch.

