Zapraszam na kolejne Wpadki i wypadki!
Artykuł został zaktualizowany 29 sierpnia 2021, by dodać najnowsze dobre praktyki odnośnie przewijania.
Dzisiaj na tapet wzięty zostanie wzorzec, który swego czasu już omawiałem: nawigacja na stronie typu one-page, płynnie przewijająca do odpowiednich sekcji na stronie. Zacznijmy od prostego przykładu:
<nav class="navigation">
<ul class="navigation__menu menu">
<li class="menu__item">
<a href="#" id="about-me-link" class="menu__link">About me</a>
</li>
<li class="menu__item">
<a href="#" id="offer-link" class="menu__link">Offer</a>
</li>
<li class="menu__item">
<a href="#" id="contact-link" class="menu__link">Contact</a>
</li>
</ul>
</nav>
[…]
<script src="http://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script>
$( '.menu__link' ).on( 'click', ( evt ) => {
const targetId = evt.target.id.replace( /-link$/, '' );
const target = $( '#' + targetId );
$( 'html, body' ).animate({
scrollTop: target.offset().top
} );
} );
</script>
Tego typu rozwiązanie spotykam na sporej liczbie stron, które wrzucane są do oceny na grupach na FB czy forach. Choć działa, jest z nim szereg problemów:
-
URL-e są częścią UI. Psując kotwice do poszczególnych sekcji strony, psujemy tak podstawowe rzeczy, jak m.in. możliwość podzielenia się linkiem do konkretnej sekcji naszej strony, np. kontaktu.
-
Brak poprawnego URL-a obniża znacząco także i użyteczność strony, nie pozwalając choćby otworzyć danej sekcji w nowej karcie. Bardzo często klikam linki w nawigacji kółkiem myszy i bardzo nie lubię, gdy okazuje się, że link w nawigacji przeniósł mnie nigdzie.
-
Co więcej, uzależniamy działanie naszej nawigacji od działania JS-a – a ten lubi się wysypać. Ba, nawigacja zacznie działać dopiero, gdy JS się wczyta, więc skonsternowany użytkownik może przez kilka sekund klikać w niedziałające odnośniki. A jeśli jest bardziej paranoiczny i ma włączonego choćby NoScripta, to linki nigdy nie zaczną działać.
-
Bez zmiany URL-a nie zmienia się także focus na stronie. A to oznacza, że zostaje w menu. Można to łatwo przetestować na przykładzie, po prostu naciskając Tab po przejściu do dowolnej sekcji. Zamiast przenieść focus na przycisk wewnątrz danej sekcji, zostaniemy przerzuceni z powrotem na górę strony, do nawigacji. Istnieją co prawda obejścia tego problemu, ale nie rozwiązują one z kolei pozostałych wymienionych problemów, zwłaszcza braku zmiany URL-a.
-
Tego typu animacja przewijania nie zważa na to, czy użytkownik nie włączył przypadkiem ograniczenia ruchu w swoim systemie.
Jak zatem zrobić taką nawigację poprawnie? Oto przykład:
<head>
[…]
<style>
@keyframes smoothscroll1 {
from, to { scroll-behavior: smooth; }
}
@keyframes smoothscroll2 {
from, to { scroll-behavior: smooth; }
}
:root {
animation: smoothscroll1 1s;
}
:root:focus-within {
animation-name: smoothscroll2;
scroll-behavior: smooth;
}
@media ( prefers-reduced-motion ) {
:root, :root:focus-within {
animation: none;
scroll-behavior: auto;
}
}
.section:focus {
outline: none;
}
.section:target {
background-color: red;
}
</style>
</head>
<body>
[…]
<nav class="navigation">
<ul class="navigation__menu menu">
<li class="menu__item">
<a href="#about-me" class="menu__link">About me</a>
</li>
<li class="menu__item">
<a href="#offer" class="menu__link">Offer</a>
</li>
<li class="menu__item">
<a href="#contact" class="menu__link">Contact</a>
</li>
</ul>
</nav>
<section id="about-me" class="section" tabindex="-1">
<h2 class="section__title">About me</h2>
<p><button>Focus me</button></p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Animi distinctio quam, ipsum eius qui vel doloribus earum iusto eos deserunt alias aspernatur voluptatem soluta? Soluta culpa doloremque repellat possimus inventore.</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod cumque maxime aliquid nihil sequi molestiae quaerat quidem dicta. Natus aliquam perspiciatis eaque? Quia modi soluta libero perferendis corporis, quaerat voluptate.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Unde culpa, eum tempora officia voluptates hic in asperiores sapiente veritatis eius officiis praesentium aliquid sint possimus, sit iste itaque repudiandae eos?</p>
</section>
[…]
<script>
{
const isReducedMotion = window.matchMedia( '( prefers-reduced-motion )' ).matches;
if ( !( 'scrollBehavior' in document.documentElement.style ) && !isReducedMotion ) {
import( 'https://unpkg.com/smoothscroll-polyfill@0.4.4/dist/smoothscroll.js' ).then( () => {
document.querySelector( '.navigation' ).addEventListener( 'click', ( evt ) => {
const link = evt.target.closest( '.menu__link' );
if ( !link ) {
return;
}
evt.preventDefault();
const scrollToElement = link.getAttribute( 'href' );
document.querySelector( scrollToElement ).scrollIntoView( {
behavior: 'smooth' }
);
location.href = scrollToElement;
} );
} );
}
}
</script>
</body>
-
Wszystkie linki w nawigacji mają poprawne URL-e, dzięki czemu można je udostępnić czy otworzyć w nowej karcie.
-
Dzięki poprawnym URL-om odpada także problem uzależnienia działania nawigacji od JS-a. Jeśli JS z jakiegoś powodu się nie doczyta, linki w nawigacji będą dalej działać. I to działać od samego początku, bez konieczności czekania na doczytanie jakiegokolwiek skryptu.
-
Choć samo dodanie hashu w adresie powinno przenosić focus, wymuszamy to poprzez dodanie atrybutu
[tabindex=-1]
do każdej sekcji. To chyba jedyny przypadek, w którym można usunąć style dla:focus
(bo aktualnie podlinkowaną sekcję i tak pewnie chcemy jakoś ostylować). -
Dodanie animowanego przewijania tylko wtedy, gdy focus jest na którymś elemencie strony (
:focus-within
), naprawia problem z płynnym przewijaniem w czasie szukania treści na stronie. -
Wykorzystanie media query
prefers-reduced-motion
pozwala na wyłączenie animacji przewijania u użytkowników, którzy nie życzą sobie animacji. -
Bonus: podlinkowanym sekcjom można nadać osobne style dzięki zastosowaniu pseudoklasy
:target
.
Co więcej, poprawnie zrobiony link tego typu nie potrzebuje ani linijki JS-a. Wszystko dzięki (nie tak już znowu) nowej własności CSS: scroll-behavior: smooth;
. Wsparcie dla niej jest bardzo dobre. A jeśli już koniecznie musimy też zapewnić wsparcie dla Safari, można dołączyć dla niego (i tylko niego) polyfilla – co robi skrypt z przykładu. W przypadku IE myślę, że można sobie darować płynne przewijanie.
I to by było na tyle!