Taky máte v CSS rádi jednotku vw? Volkswagen
mezi jednotkami? A víte, že je úplně, ale úplně nahovno?
Výraz 100vw znamená sto procent šířky viewportu. Celá
plocha prohlížeče. Chcete, aby váš full-bleed cover měl přesně tuhle
šířku? Stačí mu nastavit width: 100vw.
A co když má stránka svislý scrollbar? Uživatelé Maců ho moc nevnímají, protože macOS ho při nečinnosti automaticky schovává. Windows ho zobrazují trvale. Jde o (obvykle) 15pixelový pruh vpravo, který do šířky viewportu logicky nepatří. Nic v něm nevykreslíte. Naopak, prostor viewportu ubírá.
Jenže 100vw je šířka včetně scrollbaru!
Co to znamená? Vyrobíte si na Macu full-bleed cover,
width: 100vw, otevřete to v Chrome na Windows a hele, stránka
horizontálně scrolluje. Cover přečnívá za pravý okraj, přesně
o těch 15 pixelů scrollbaru.
Základní jednotka pro šířku viewportu, která nerespektuje šířku viewportu.
Tenhle bug je tu už dlouho a nikdo s ním nehne. Tiket v CSS Working Group visí v limbu od roku 2021 a podobné diskuze běží ještě déle. Browser vendors říkají „spec to nařizuje“, spec autoři říkají „browsery to interpretují špatně“ a webový vývojář mezitím píše workaround na úplně banální full-bleed obrázek.
Mezitím přibyly další jednotky (svw, lvw,
dvw) a žádná z nich scrollbar nezohledňuje. Všechny nové
jednotky jsou na hovno.
Jak tedy ten full-bleed cover udělat, aby se na Windows nezadělalo na horizontální scroll? Workaroundů existuje hned několik. Pojďme si je projít od toho nejhloupějšího.
Slepé uličky
První instinkt: vynutit svislý scrollbar trvale a odečíst jeho šířku.
html {
overflow-y: scroll;
}
.cover {
width: calc(100vw - 15px); // NEFUNGUJE
}
Tohle by fungovalo, pokud by scrollbar měl skutečně 15px. Což nemá na Macu, mobilech (0px) a uživatelů s vlastním stylováním.
Druhý pokus: scrollbar-gutter: stable. Říká prohlížeči
„vždycky rezervuj místo na scrollbar, ať už ho zobrazíš, nebo ne“.
Konzistentní layout, žádné poskakování při přepínání mezi krátkou a
dlouhou stránkou. Jenže 100vw to neopraví – ten zahrnuje
šířku scrollbaru bez ohledu na to, jestli je vidět.
Závěr: šířku scrollbaru musíme změřit.
Řešení 1: změřit ho JavaScriptem
Property document.documentElement.clientWidth vrací vnitřní
šířku HTML elementu a scrollbar do ní nezahrnuje. Propíšeme ji do CSS
proměnné a aktualizujeme při každém resize:
<script>
new ResizeObserver(() => {
document.documentElement.style.setProperty(
'--viewport-width',
document.documentElement.clientWidth + 'px'
);
}).observe(document.documentElement);
</script>
A pak jen doplníme do CSS defaultní hodnotu a můžeme proměnnou využívat:
:root {
--viewport-width: 100vw; // default
}
.cover {
width: var(--viewport-width);
}
Funguje na všem. Triggne se i v okamžiku, kdy scrollbar uprostřed života stránky náhle vznikne nebo zmizí. Třeba když uživatel rozbalí dlouhý seznam. Pár řádků JS a hotovo.
Existuje ale i čistě CSS cesta.
Řešení 2: zapomeňte na vw, máte cqw
V roce 2022 přišly do CSS container query units.
100cqw znamená „100 % inline-size nejbližšího container-query
kontejneru“. Stačí jako container určit body, jehož šířka
scrollbar nezahrnuje:
body {
container-type: inline-size;
}
.cover {
width: 100cqw;
}
Žádný workaround, žádný horizontální scroll. Prostě jednotka, která dělá co slibuje.
Háček: vnořené containery
Tohle platí jen pokud body zůstane jediný container-query
kontejner v subtree. Jakmile někde uvnitř použijete
container-type pro komponentu (typicky abyste měli responsive
komponenty nezávislé na šířce okna), cqw uvnitř té
komponenty se začne vztahovat k té komponentě, ne k body.
Většině webů to nevadí. Container queries na komponenty klidně mít můžete, jen v nich obvykle nepotřebujete full-bleed cover. Ten patří do hlavního layoutu, ne do karty produktu nebo sidebaru.
Pokud ale stavíte opravdu obecný framework, kde si full-bleed může vyžádat libovolná komponenta kdekoliv, omezení to být může. A pro ten případ je tu poslední finta.
Řešení 3: registrovaná @property
Vyrobíme si CSS proměnnou typu length. Důležité slovo: typu.
Registrovaná @property se počítá jako length už při
deklaraci a dál se dědí jako konkrétní pixely – nezávisle na tom,
kolik containerů kdo cestou nasázel:
@property --viewport-width {
syntax: '<length>';
inherits: true;
initial-value: 0px;
}
html {
container-type: inline-size;
}
body {
--viewport-width: 100cqw;
}
.cover {
width: var(--viewport-width);
}
Bez registrace by to nepomohlo, protože proměnná by se substituovala
textově a 100cqw by se resolvlo až v místě použití, tedy
v nejbližším containeru. S registrací typu length se hodnota
zmrazí a dědí se nezávisle.
Až jednou někdo zavře ten tiket v CSS WG, přijde sluneční den. Do té
doby zapomeňte, že vw existuje. 🙂
Napište komentář