Když uživatel posouvá stránku, málokdy se mu povede, že posun skončí přesně na začátku odstavce nebo obrázku, což pak zhoršuje UX (dojem ze stránky). Do teď se podobné situace řešili pomocí javaskriptu, ale kombinace JS a skrolování většinou UX ještě zhorší.
Díky nové CSS specifikaci Scroll snap můžete tyto situace vyřešit automaticky jen pomocí CSS definice.
Nová specifikace je již podporována v prohlížečích založených na Webkit (tedy Chrome, Safari a nový Edge; Opera zatím ne). Firefox momentálně podporuje starší zamítnutou specifikaci. Firefox od verze 68 podporuje již novou specifikaci. Podpora ve starém Internet Explorer a Edge s MS jádrem viz níže.
Typ přichycení
Základní vlastností, pomocí které určíte, že se daný prvek má skrolovat, je scroll-snap-type
společně s overflow: scroll
(nebo auto
). Vlastnost očekává kombinaci dvou hodnot. První je jedna z NONE
, X
, Y
a BOTH
(resp. místo X
a Y
lze použít logické hodnoty block
a inline
) a určuje, ve kterém směru se má skrolovat (nebo zda v obou směrech či žádném), a druhá pak určuje, jestli se má posun na daném bodě zastavit pokaždé (mandatory
) nebo jen při přiblížení se k němu (proximity
):
body { //stránka se skroluje bez zachytávání
scroll-snap-type: none; //výchozí hodnota
}
article {
overflow: hidden;
overflow-y: auto;
scroll-snap-type: Y proximity; //fyzické
scroll-snap-type: block proximity; //logické
}
.gallery {
overflow: hidden;
overflow-x: scroll;
scroll-snap-type: X mandatory; //fyzické
scroll-snap-type: inline mandatory; //logické
}
[/css]
Příklad říká, že svislý posun článku se má přichytit, jen když posun skončil poblíž začátku nadefinovaného prvku (např. odstavce). V opačném případě (např. dlouhý odstavec, který se nevejde na obrazovku) se přichycení neprovede. Naopak horizontální posun obrázků (vedle sebe) se musí vždy přesně zastavit na požadovaném bodě.
V praxi rozdíl mezi mandatory
a proximity
vypadá tak, že u mandatory
se prostor mezi prvky rozdělí na polovinu a kdykoliv posun skončí, rozhodne se, zda je blíže předchozí nebo následující položce a podle toho se provede přichycení. Naopak u proximity
se vyhradí určitá oblast (třeba +/- 50px) kolem začátku prvku, který se má přichytit a pokud posun skončil v této oblasti, přichycení se provede; pokud ale posun skončil mimo jakoukoliv oblast přichycení, posun skončí bez něj.
Všimněte si, že typ uchycení vždy definujeme na kontejneru, který se posouvá!
Body uchycení
Když máte určený typ přichycení, musíme určit body, ke kterým se bude posun přichycovat.
Body přichycení nastavíme prvkům uvnitř kontejneru, který se posouvá pomocí vlastnosti scroll-snap-align
:
article > p {
scroll-snap-align: start;
}
.gallery > img {
scroll-snap-align: center;
}
.gallery > img:last-child {
scroll-snap-align: end;
}
Vlastnost scroll-snap-align
má tři možné hodnoty, které určují, zda se u daného prvku posun zastaví u jeho začátku (start
), prostředku (center
) nebo konce (end
). V příkladu si tedy všimněte, že u článku se posun zastaví vždy na začátku odstavce, zatímco u galerie se zastaví tak, aby byl obrázek vždy vycentrovaný. Výjimku máme u posledního obrázku galerie, kde se má posun zastavit tak, aby byl poslední obrázek zarovnaný ke konci galerie.
Pozor na nepřístupný obsah
Jakmile nadefinujete povinné (mandatory
) body zachycení, bude se jimi prohlížeč řídit a bude obsah posouvat mezi nimi. Pokud tedy např. určíte uchycení na začátky odstavců, ale budete mít v textu odstavec delší než je jedna obrazovka (např. u mobilního zařízení), nebude si moci uživatel posunou text tak, aby si přečetl konec odstavce (protože obsah přeskočí automaticky na další odstavec). Povinné přichycení by se tedy mělo používat pouze na prvky jako jsou obrázky (či obdobné prvky), které se mohou zmenšit a přizpůsobit na velikost obrazovky.
Povinné body uchycení je potřeba používat s rozvahou a pouze tam, kde se prvky mohou zmenšit tak, aby se vešli na obrazovku!
Pokud si nejste jisti, kombinujte povinné přichycení s CSS podmínkou @media (min-*)
tak, aby se aplikovaly jen na dostatečně velkých zařízeních a monitorech.
@media (min-height: 400px) {
/* na příliš nízkých displejích
se nebudou odstavce přichytávat
k okrajům obrazovky */
article {
overflow: hidden;
overflow-y: auto;
scroll-snap-type: Y mandatory;
}
}
Na druhou stranu Scroll snap bylo navrženo právě pro použití na malých mobilních zařízeních (malý berte s rezervou, myslí se tím současné 6″ phablety), na kterých může být problém posouvat stránky s galeriemi a dalšími grafickými prvky, takže výše uvedené omezení na rozlišení nemusí dávat smysl a spíše byste se měli zamyslet nad tím, jak obsah pro mobilní zařízení přizpůsobit.
Okraje
Tak, jako můžete u DIVů, odstavců apod. určit, jaké mají mít okraje (padding
a margin
), aby jejich obsah pěkně pasoval do stránky, můžete stejně určit okraje zachycení (tedy vzdálenost od okraje):
article {
scroll-padding: 2em;
}
Tento příklad dává smysl pouze v kombinaci s předchozími příklady. U článku jsme nastavili, že posun se má přichytit k začátku odstavce. V kombinaci s vlastností scroll-padding
pak říkáme, že za začátek odstavce považujeme místo o (zhruba) 2 řádky výše než je skutečný začátek odstavce. V praxi to tedy znamená, že posun se zastaví tak, že z předchozího odstavce bude zobrazen zhruba 1 až 2 řádky a další odstavec bude tedy vidět vždy od začátku. Tím vytvoříme pěkně čitelný text, protože uživatel se bude moci přesvědčit, že čte pokračování textu a že stránka třeba nepřeskočila o dva nebo více odstavců.
Padding posunu určujeme na kontejneru, který se posouvá!
Naopak pokud chcete nastavit pro každý prvek jiný posun, můžeme prvkům v kontejneru určit okraj posunu vlastností scroll-margin
:
.gallery > img {
scroll-margin: 10px;
}
.gallery > img:last-child {
scroll-margin: 0;
}
Pro galerii, kde se posun zarovnává na prostředek obrázků říkáme, že obrázek se má brát jako že je o 10 pixelů větší. Pokud by se tedy nevešel celý do stránky (viewport kontejneru), bude zarovnaný doleva a odsazený o 10px doprava. Druhé pravidlo pak říká, že poslední obrázek se má zarovnat přesně s koncem kontejneru.
Margin posunu vždy určujeme na prvcích uvnitř kontejneru!
Obě vlastnosti scroll-padding
a scroll-margin
se chovají stejně jako původní margin
a padding
a je tedy možno nadefinovat různé rozměry pro různé strany:
article { /* okraj jen nahoře */
scroll-padding: 0;
scroll-padding-top: 2em;
}
.gallery { /* okraj jen vlevo */
scroll-margin: 0 0 0 10px;
}
Jak už bylo zmíněno výše, Scroll Snap byl již navržen s ohledem na logické vlastnosti, takže místo např. scroll-padding-top
můžete použít vlastnost scroll-padding-block-start
.
Podpora v prohlížečích
Vlastnosti pro padding
a margin
nemusejí být podporovány ve všech prohlížečích, které Scroll snap podporují (např. v Chrome 72 zatím nejsou podporovány).
Funkčnost byla prvně implementována ve Webkit, takže podporu má (nyní v 2018/2019) Chrome od verze 69+ (září 2018; na Android o něco dříve) a Safari 11+ (září 2017). Opera a ostatní prohlížeče využívající webkit (např. Samsung Internet) ale tato vlastnost zatím nezapnuly (experimental flag). Starý Firefox (39+) a Edge (12-18) podporují starší verzi specifikace, takže něco nemusí fungovat přesně tak, jak byste očekávali. Nové verze Firefox 68 (červenec 2019) a Edge 76 (konec 2019) již podporují novou specifikaci.
Při použití scroll snap vlastností je potřeba použít prefix -ms-
pro podporu v Edge (IE10 a IE11 také podporují prefix -ms-
, ale jen když je detekována dotyková obrazovka).
V Chrome 71, které Scroll snap podporuje, jsem se setkal s tím, že po načtení stránky sice přichycení fungovalo, ale po delší době nebo po změně obsahu stránky přes JS/AJAX se přichycení rozbilo a kontejnery se posouvali bez něj.
Javascript
Společně s CSS vlastnostmi přichází i JS API pro posun pomocí programu (resp. obě funkce již nějakou dobu existují, ale nyní podporují zachytávání).
Pokud chcete posunout kontejner na určitý prvek, použijte jeho funkci scrollIntoView()
, která bere v úvahu nastavené body zachytávání a okraje (scroll-padding
a scroll-margin
):
<a href="#example"
onclick=document.getElementById('example')
.scrollIntoView(); return false;">
Zobrazit příklad
</a>
Pozor na to, že funkce scrollIntoView()
nemusí existovat. V uvedeném příkladu bude vše fungovat, protože pokud funkce neexistuje, JS se neprovede a odkaz bude fungovat postaru pomocí href
na ID prvku. Ve složitějších skriptech ale budete muset kontrolovat, zda funkce existuje, a pokud ne, provést posun postaru pomocí scrollTop
nebo scrollLeft
.
Stejně funguje i funkce scrollTo(X, Y)
, která taktéž bere v úvahu nadefinované body přichycení:
//posune stránku na konec
// (s ohledem na přichycení tam,
// kde to prohlížeč podporuje)
window.scrollTo(
0, document.body.getBoundingClientRect().y
);
Funkce scrollIntoView()
má několik parametrů (předávaných v objektu): parametr behavior
(pozor, není tam „u“!) může mít hodnoty instant
(provede skok bez animace) nebo smooth
(plynule posune stránku pomocí animace). Výchozí hodnota je auto
(podle vzdálenosti); parametry block
a inline
určují, kam se prvek zarovná. Hodnota block
určuje zarovnání bloků (většinou nahoru nebo dolu) a inline
zarovnání v řádce (tedy doleva a doprava). Hodnoty jsou start
, center
, end
a nearest
, kde start
a end
určuje zarovnání nahoru nebo dolu resp. doleva nebo doprava. Hodnota center
určuje zarovnání prvku na střed prostoru a nearest
zarovná prvek k bližšímu z okrajů. Výchozí hodnoty jsou block: center
a inline: nearest
.
Parametr behavior
podporují pouze prohlížeče s podporou scroll-snap-align
. Některé prohlížeče nepodporují hodnoty center
a nearest
(výchozí hodnoty jsou pak start
). Hodně staré prohlížeče (IE8+) podporují místo objektu pouze boolean, kde TRUE
je zarovnání nahoru a FALSE
zarovnání dolů (výchozí je TRUE
).
el.scrollIntoView({
behavior: smooth,
block: start, //nahoru (tedy k první řádce)
inline: center //na střed v řádce
}
//Jednoduché volání (IE8+)
e.scrollIntoView(true); //zarovnání nahoru
Feature detect
Pro ověření, zda prohlížeč podporuje přichytávání, můžete použít CSS definici @supports()
nebo JS funkci CSS.supports()
. Pozor ale na to, že vlastnost scroll-snap-type
byla nadefinována již v první verzi, kterou podporují starší prohlížeče. Pro ověření nové verze je potřeba použít vlastnost scroll-snap-align
:
@supports (scroll-snap-align: start) {
/* ... definice přichytávání */
}
function scrollIntoView(el) {
if ('CSS' in window &&
CSS.supports('scroll-snap-align',
'start')) {
//scroll pomocí bodů zachycení
el.scrollIntoView();
}
else { //normální scroll na pozici prvku
window.scrollTo(0, el.offsetTop);
}
}
//volání vytvořené funkce
window.scrollIntoView(
document.getElementById('example')
);