Skip to content

Wszystko o WordPressie, tworzeniu stron — i nie tylko

🖱 Płynne przewijanie do kotwicy: CSS, JavaScript i jQuery — trzy działające podejścia

🖱 Płynne przewijanie do kotwicy: CSS, JavaScript i jQuery — trzy działające podejścia

Użytkownik klika link z kotwicą i strona natychmiast przeskakuje w odpowiednie miejsce. Żadnej animacji, żadnej płynności, żadnego zrozumienia, gdzie się znalazł. Szarpane przejście dezorientuje, szczególnie na długich landing page'ach i w dokumentacji.

Dodać płynne przewijanie do kotwicy można w 5 minut. Ale diabeł tkwi w szczegółach: link może prowadzić z zewnętrznej strony, w URL-u jest już hash, a na górze wisi stały nagłówek, który zasłania kotwicę. Zwykłe scroll-behavior: smooth tych niuansów nie rozwiązuje.

Poniżej trzy działające podejścia: czysty CSS, natywny JavaScript i jQuery. Każde z kodem, który można pobrać i wkleić do projektu. Plus osobno omawiamy przejścia zewnętrzne, aby przewijanie działało, nawet gdy użytkownik przychodzi z innej strony lub z wyników wyszukiwania.

💡 Szybki przegląd:

  • Włączamy płynne przewijanie jedną linijką CSS, bez JavaScriptu
  • Dodajemy odstęp pod stały nagłówek przez scroll-margin-top
  • Piszemy natywny JS z scrollIntoView, do przejść zewnętrznych i precyzyjnego dostrojenia
  • Omawiamy wariant jQuery z animate(), gdy potrzebna jest niestandardowa prędkość i kontrola
  • Obsługujemy zewnętrzny hash: strona się załadowała, kotwica jest w URL-u i płynnie jedziemy do celu

Po co w ogóle płynne przewijanie

Gwałtowny skok do kotwicy dezorientuje. Użytkownik traci kontekst: gdzie byłem, gdzie trafiłem, co pominąłem? Płynne przewijanie zachowuje orientację przestrzenną, oko ma się czego uchwycić, gdy strona się przesuwa.

Z punktu widzenia percepcji strona z płynnym przewijaniem wygląda na dopracowaną. Jest to szczególnie widoczne na długich stronach: landing page'e, dokumentacja, sekcje FAQ, spisy treści artykułów. Użytkownik klika „Cennik" w menu, a strona nie szarpie, tylko miękko zjeżdża do bloku z cenami.

Technicznie zadanie dzieli się na dwa: przewijanie przy kliknięciu w link wewnętrzny (użytkownik jest już na stronie) i przewijanie przy przejściu zewnętrznym (w URL-u od razu jest #anchor). W drugim przypadku przeglądarka domyślnie skacze do kotwicy natychmiast, jeszcze przed pełnym załadowaniem, i trzeba to przechwycić.

CSS scroll-behavior, jedna linijka zamiast skryptu

Najprostszy i najnowocześniejszy sposób. Przeglądarki nauczyły się płynnego przewijania natywnie, wystarczy jedna reguła CSS:

1html {
2 scroll-behavior: smooth;
3}

To wszystko. Każde przejście przez link kotwiczący staje się płynne. Żadnego JavaScriptu, żadnych bibliotek.

Wsparcie: wszystkie nowoczesne przeglądarki od 2022 roku: Chrome 61+, Firefox 36+, Safari 15.4+, Edge 79+. Jedyny wyjątek, IE11, ale jego udział w ruchu dąży do zera.

Stały nagłówek: scroll-margin-top

Jeśli nad treścią wisi stały nagłówek (sticky header), kotwica po przewinięciu znajdzie się pod nim. Rozwiązuje się to jedną linijką:

1h2, h3, [id] {
2 scroll-margin-top: 80px; /* высота хедера + отступ */
3}

Przeglądarka sama doda odstęp podczas przewijania do elementu docelowego. Kiedyś pisano do tego obejścia z padding-top i ujemnym margin, teraz nie trzeba.

Ograniczenia czystego CSS

Podejście CSS nie daje kontroli nad prędkością animacji i funkcją wygładzania (easing). Przeglądarka używa wbudowanej krzywej, zazwyczaj ease-in-out. Jeśli potrzebna jest niestandardowa prędkość lub nieliniowa animacja, przechodzimy do JavaScriptu.

Natywny JavaScript: scrollIntoView

Gdy sam CSS nie wystarcza, z pomocą przychodzi metoda scrollIntoView() z opcją behavior: 'smooth':

1document.querySelectorAll('a[href^="#"]').forEach(anchor => {
2 anchor.addEventListener('click', function(e) {
3 e.preventDefault();
4 const target = document.querySelector(this.getAttribute('href'));
5 if (target) {
6 target.scrollIntoView({
7 behavior: 'smooth',
8 block: 'start'
9 });
10 }
11 });
12});

Ten kod przechwytuje kliknięcie we wszystkie linki z #, anuluje standardowe przejście i uruchamia płynne przewijanie. Działa bez jQuery i bez dodatkowych bibliotek.

Zaleta podejścia: można sterować programowo: zmieniać prędkość przez CSS scroll-behavior na <html>, dodawać warunki (na przykład wykluczać określone linki), łączyć z scroll-margin-top dla nagłówka.

Wada: scrollIntoView nie pozwala bezpośrednio ustawić prędkości animacji. Dla niestandardowego czasu przewijania (powiedzmy 800 ms zamiast przeglądarkowych ~500 ms) potrzebny jest albo jQuery, albo ręczny requestAnimationFrame.

JQuery animate, pełna kontrola nad animacją

Jeśli projekt już używa jQuery lub potrzebne jest precyzyjne dostrojenie (prędkość, odstęp pod nagłówek, filtrowanie linków), oto aktualny skrypt. Poniżej zaadaptowana wersja klasycznego podejścia, przepisana pod nowoczesne jQuery:

1jQuery(document).ready(function($) {
2 // Плавный скролл при клике на якорную ссылку
3 $('a[href*="#"]:not([href="#"])').on('click', function() {
4 if (
5 location.pathname.replace(/^\//, '') === this.pathname.replace(/^\//, '')
6 && location.hostname === this.hostname
7 ) {
8 var target = $(this.hash);
9 target = target.length ? target : $('[name="' + this.hash.slice(1) + '"]');
10 if (target.length) {
11 $('html, body').animate({
12 scrollTop: target.offset().top - 80
13 }, 800);
14 return false;
15 }
16 }
17 });
18
19 // Скролл при внешнем переходе с хешем в URL
20 if (window.location.hash) {
21 var hash = window.location.hash.substring(1);
22 var $target = $('#' + hash);
23 if ($target.length) {
24 setTimeout(function() {
25 $('html, body').animate({
26 scrollTop: $target.offset().top - 80
27 }, 800);
28 }, 100);
29 }
30 }
31});

Co tu się zmieniło w stosunku do starych wersji krążących po blogach:

  • .bind() zastąpiono przez .on(), .bind() jest uznane za przestarzałe od jQuery 3.0 i zostanie usunięte w jQuery 4.0.
  • Selektor 'a:not(.spu-clickable)[href*="#"]:not([href="#"])' uproszczono, specyficzna klasa .spu-clickable dotyczyła konkretnej wtyczki (Popups by OptinMonster) i w ogólnym przypadku nie jest potrzebna.
  • jQuery(window).bind("load", ...) usunięto, druga część skryptu dla zewnętrznego hashu żyje teraz w $(document).ready() i nie potrzebuje osobnego zdarzenia load.
  • Dodano opóźnienie setTimeout(..., 100), aby DOM na pewno się wyrenderował przed obliczeniem pozycji kotwicy (istotne dla stron z leniwym ładowaniem obrazów i dynamiczną treścią).

Gdzie wstawić kod

Trzy warianty do wyboru:

  • Przez plik motywu. Dodaj skrypt w functions.php przez wp_enqueue_script(), standardowy sposób dla motywu potomnego.
  • Wtyczka Code Snippets. Wstaw kod jako nowy snippet z autostartem, nie zginie podczas aktualizacji motywu.
  • Niestandardowa wtyczka JS. Jeśli na stronie jest już używana wtyczka do wstawiania dowolnego JavaScriptu (na przykład WPCode), dodaj kod tam.

Obsługa zewnętrznego przejścia z hashem, szczegółowa analiza

Najtrudniejsza część zadania. Użytkownik przechodzi przez link https://site.com/page/#pricing z innej strony lub z wyszukiwarki. Przeglądarka domyślnie natychmiast skacze do #pricing, gdy tylko element pojawi się w DOM, bez animacji i często przed pełnym załadowaniem strony.

Powyższy skrypt jQuery rozwiązuje to w dwóch krokach:

  • Określa obecność hashu przez window.location.hash. Jeśli hash istnieje, odczytuje jego wartość (bez #).
  • Szuka celu i przewija. $('#' + hash) znajduje element, setTimeout daje stronie czas na wyrenderowanie, animate() prowadzi do celu z odstępem 80 pikseli.

Opóźnienie 100 milisekund jest empiryczne. Na szybkich stronach wystarczy i 50 ms, na ciężkich (landing page'e z wideo w tle, mapami, wykresami) lepiej zwiększyć do 200-300 ms. Alternatywa, poczekać na zdarzenie window.load zamiast document.ready, ale wtedy przewijanie nastąpi dopiero po załadowaniu wszystkich obrazów, co może się przeciągnąć.

Co robić, jeśli kotwica nie znajduje celu

Czasami hash w URL-u wskazuje na nieistniejący element. Na przykład użytkownik się pomylił lub stronę zmieniono. W tym przypadku ani CSS, ani podejście JS nie wywrócą się z błędem, po prostu nic się nie stanie. Przeglądarka zignoruje nieistniejącą kotwicę. Płynne przewijanie też po cichu zadziała (warunek if (target.length) nie przepuści dalej).

Porównanie trzech podejść

Kryterium

CSS scroll-behavior

JS scrollIntoView

jQuery animate

Złożoność wdrożenia

1 linijka CSS

10-15 linijek JS

25-30 linijek JS + jQuery

Kontrola prędkości

Brak

Brak (przeglądarkowa)

Tak (ms)

Zewnętrzny hash

Tak, natywnie

Wymaga dodatkowego kodu

Wymaga dodatkowego kodu

Odstęp pod nagłówek

scroll-margin-top

scroll-margin-top

Ręczny w offset().top

Zależności

Brak

Brak

jQuery 3.x

Wsparcie przeglądarek

96%+

96%+

99%+ (z jQuery)

Dla większości nowoczesnych projektów optymalne jest podejście CSS: jedna linijka, zero zależności, natywna wydajność. Jeśli potrzebny jest odstęp pod nagłówek, dodaje się scroll-margin-top. Jeśli potrzebna jest niestandardowa prędkość lub wsparcie starych przeglądarek, jQuery z animate().

Poniżej wideo z demonstracją na żywo wszystkich trzech podejść, od CSS do jQuery:

⁉️🤔 Często zadawane pytania

Dlaczego płynne przewijanie nie działa na urządzeniach mobilnych?

W iOS Safari do wersji 15.4 scroll-behavior: smooth nie było wspierane. Obecnie wsparcie jest (Safari 15.4+, marzec 2022). Jeśli przewijanie nie działa na starych iPhone'ach, dodaj JavaScriptowy fallback z scrollIntoView lub requestAnimationFrame. Sprawdź też, czy przewijania nie blokuje overflow: hidden na body, niektóre mobilne menu ustawiają to podczas otwierania. Na czerwiec 2026 roku globalne wsparcie scroll-behavior wynosi 96,3% przeglądarek (dane Can I Use).

Jak zrobić płynne przewijanie nie do kotwicy, a do dowolnego elementu po kliknięciu przycisku?

Nadaj elementowi id i użyj tego samego JavaScriptu, ale przypisanego do przycisku, a nie do linku kotwiczącego. Na przykład przycisk „Powrót na górę": document.querySelector('#back-to-top').addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })). Dla wariantu jQuery: $('#back-to-top').on('click', () => $('html, body').animate({ scrollTop: 0 }, 600)). Żadne kotwice nie są potrzebne, przewijamy do współrzędnej.

Czy trzeba dołączać jQuery tylko dla płynnego przewijania w 2026 roku?

Nie. jQuery waży około 87 KB w wersji min (30 KB gzip). Jeśli na stronie nigdzie indziej nie używa się jQuery, dołączanie go dla jednego przewijania jest nieracjonalne. Proszę wziąć podejście CSS z scroll-behavior: smooth lub natywne scrollIntoView. jQuery jest uzasadnione tylko wtedy, gdy strona jest już na nim zbudowana (motywy na jQuery, projekty legacy) i chcą Państwo dodać przewijanie do istniejącej bazy kodu.

Czy można animować przewijanie z różną prędkością: najpierw szybko, potem wolno?

Tak, przez $.animate() z jQuery UI lub przez requestAnimationFrame z funkcją wygładzania (easing). Standardowe jQuery animate() używa funkcji liniowej lub swing. Dla niestandardowych krzywych proszę podłączyć jquery.easing lub napisać własną pętlę na requestAnimationFrame z niestandardową funkcją wygładzania, na przykład easeInOutQuad. Daje to pełną kontrolę nad prędkością, trajektorią i nie wymaga zewnętrznych bibliotek.

Jak sprawdzić, czy przewijanie działa podczas zewnętrznego przejścia z hashem?

Proszę otworzyć nową kartę, wkleić URL z kotwicą (na przykład https://вашсайт.ру/page/#contacts) i nacisnąć Enter. Strona powinna się załadować i płynnie przewinąć do bloku. Druga opcja: w konsoli przeglądarki na otwartej stronie wykonać location.hash = '#contacts'. Jeśli przewijanie jest płynne, skrypt działa. Jeśli strona szarpnęła natychmiast, obsługa zewnętrznego hashu nie jest skonfigurowana.

Jakie podejście wybrać do swojego zadania

Jeśli strona jest nowoczesna, bez jQuery, **CSS scroll-behavior: smooth pokrywa zdecydowaną większość scenariuszy. Jedna linijka, natychmiastowy rezultat. Proszę dodać scroll-margin-top pod nagłówek i gotowe.

Jeśli strona na WordPress z jQuery w motywie, **skrypt jQuery z animate(). Daje kontrolę nad prędkością i odstępem, obsługuje przejścia zewnętrzne, nie koliduje z innymi wtyczkami.

Jeśli piszą Państwo od zera i nie chcą ciągnąć biblioteki, **natywny scrollIntoView() z obsługą zewnętrznego hashu. Złoty środek: czysto, szybko, bez zależności.

Proszę wziąć kod z artykułu, wkleić do projektu i sprawdzić przejście zewnętrzne, to jedyny przypadek, który łatwo pominąć podczas testowania.