Skip to content

Все для WordPress, веб-розробки — і не тільки

🔧 JQuery — плавний скрол до якоря при переході з іншої сторінки

🔧 JQuery — плавний скрол до якоря при переході з іншої сторінки

Ви додали на сторінку якір, прописали #section в URL і чекаєте гарного плавного переходу. Користувач клацає з іншої сторінки, браузер чесно підставляє хеш... і ривком перескакує в потрібне місце. Жодного скролу. Просто миттєвий стрибок.

Проблема в тому, що браузер за замовчуванням обробляє хеш миттєво, до того, як DOM повністю готовий і скрипти встигли перехопити подію. А коли користувач приходить за зовнішнім посиланням, ваш click-обробник взагалі не спрацьовує, події кліку не було, був прямий перехід за URL.

Ми зібрали два перевірені jQuery-рішення: базовий скрипт для простих випадків і просунутий варіант з обробкою кліків, завантаження сторінки та обходом конфліктів. Обидва протестовані на реальних проєктах і працюють з актуальними версіями jQuery.

💡 Швидкий огляд:

  • Зберегти хеш з URL у тимчасову змінну
  • Очистити location.hash, щоб браузер не стрибнув ривком
  • Проскролити до якоря через animate() з потрібним відступом
  • Відновити хеш для правильного URL в адресному рядку

Коли відвідувач переходить за посиланням site.com/page/#pricing, він очікує побачити блок із цінами, а не верх сторінки. Різкий стрибок дезорієнтує, користувач не розуміє, де опинився і що було до цього. Плавне прокручування дає контекст: очі встигають зачепитися за проміжні секції, мозок будує маршрут «звідки → куди».

З нативною CSS-властивістю scroll-behavior: smooth історія проста, додали в таблицю стилів, і браузер сам скролить до якоря. Але цей підхід ламається рівно в той момент, коли вам потрібен кастомний відступ під фіксовану шапку. 110 пікселів відступу, і блок не заїжджає під header, а залишається рівно там, де його чекає користувач. jQuery-рішення дає цей контроль.

Плюс, кросбраузерність. scroll-behavior: smooth не працює в Internet Explorer (так, такі проєкти ще живі), а jQuery-метод animate() відпрацьовує скрізь однаково.

Базовий скрипт: мінімум коду, максимум користі

Перший варіант, компактний, на 10 рядків. Він вирішує рівно одне завдання: плавно проскролити до якоря, коли користувач прийшов з іншої сторінки або за прямим посиланням із хешем.

1jQuery(document).ready(function ($) {
2 var myHash = location.hash; // сохраняем хеш
3 location.hash = ''; // очищаем — браузер перестаёт дёргаться
4 if (myHash[1] != undefined) { // хеш не пустой?
5 $('html, body').animate(
6 { scrollTop: $(myHash).offset().top - 110 },
7 700 // длительность в миллисекундах
8 );
9 location.hash = myHash; // возвращаем хеш обратно
10 }
11});

Розбір по кроках, тому що три рядки творять магію, але важливо розуміти механіку:

  • var myHash = location.hash, читаємо хеш з адресного рядка (наприклад, #pricing). На цьому етапі браузер ще нічого не скролить.
  • location.hash = '', ключовий трюк. Очищаємо хеш, і браузер втрачає ціль для миттєвого стрибка. Без цього рядка скрипт буде «боротися» зі вбудованою поведінкою браузера і програє.
  • $(myHash).offset().top - 110, обчислюємо позицію якоря від верху документа і віднімаємо 110 пікселів. Це ваш відступ під фіксовану шапку. Якщо шапка position: fixed висотою 80px, ставте 80 (або 90 із запасом). Якщо шапки немає, приберіть віднімання зовсім.
  • $('html, body').animate(...), плавно скролимо за 700 мілісекунд. Селектор 'html, body', не примха: різні браузери скролять або html, або body, тому пишемо обидва для надійності.
  • location.hash = myHash, повертаємо хеш. Тепер адресний рядок знову показує #pricing, а сторінка вже гарно припаркована біля потрібного блоку.

Скрипт вставляється у functions.php теми, у кастомний JS-файл або через плагін на кшталт Code Snippets. Головне, він має бути в блоці jQuery(document).ready().

Просунутий варіант: обробка кліків, завантаження сторінки та захист від конфліктів

Базовий скрипт покриває більшість сценаріїв, але бувають ситуації, коли location.hash = '' не спрацьовує: браузер встигає стрибнути до очищення хешу. Тоді підключаємо другий варіант, він важчий, але надійніший.

1jQuery(document).ready(function () {
2 jQuery(window).bind('load', function () {
3 jQuery('a:not(.spu-clickable)[href*="#"]:not([href="#"])').click(function () {
4 if (
5 location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') ||
6 location.hostname == this.hostname
7 ) {
8 var target = jQuery(this.hash);
9 target = target.length ? target : jQuery('[name=' + this.hash.slice(1) + ']');
10 if (target.length) {
11 jQuery('html, body').animate(
12 { scrollTop: target.offset().top - 37 },
13 1000
14 );
15 return false;
16 }
17 }
18 });
19 });
20});
21
22jQuery(window).load(function () {
23 function goToByScroll(id) {
24 jQuery('html, body').animate(
25 { scrollTop: jQuery('#' + id).offset().top - 38 },
26 1000
27 );
28 }
29 if (window.location.hash != '') {
30 goToByScroll(window.location.hash.substr(1));
31 }
32});

Що тут відбувається, по блоках:

Блок перший, обробник кліку по внутрішніх посиланнях з якорем. jQuery(window).bind('load', ...) гарантує, що всі елементи сторінки (включно з картинками та iframe) завантажені до прив'язки обробника. Фільтр a:not(.spu-clickable) виключає посилання, які вже обробляє попап-плагін (SPU, WordPress PopUp), щоб анімації не конфліктували. Додайте свої класи-винятки за аналогією.

Блок другий, обробка переходу із зовнішньої сторінки. jQuery(window).load() спрацьовує пізніше за document.ready і гарантує, що всі елементи, включно з якорем, уже в DOM. Функція goToByScroll(id) робить рівно те саме, що й базовий скрипт, але викликається тільки після повного завантаження вікна. substr(1) відрізає символ # від хешу.

Важливий нюанс: jQuery(window).load() вважається застарілим починаючи з jQuery 3.0. Якщо ваш проєкт на актуальній версії jQuery, замініть на:

1jQuery(window).on('load', function () {
2 // код здесь
3});

Різниця в синтаксисі, поведінка ідентична.

Тонке налаштування: відступи, селектори та підводні камені

Скрипт робочий, але кожна верстка індивідуальна. Три частих доопрацювання, які рятували на реальних проєктах.

Проблема перша, offset().top повертає не ті координати. Таке буває, коли в батьківських блоків задано CSS-властивість transform, filter або will-change, вони створюють новий контекст позиціонування, і offset() рахує відносно нього, а не документа. Рішення, варіант з offset({top: -0}):

1jQuery('html, body').animate(
2 { scrollTop: jQuery('#' + id).offset({ top: -0 }).top },
3 1000
4);

Параметр {top: -0} змушує jQuery перерахувати позицію від меж документа, ігноруючи проміжні контексти. Виглядає як магія, але працює.

Проблема друга, селектор 'html, body' скролить не той елемент. У деяких темах скрол-контейнером виступає не html або body, а конкретний блок, наприклад, body.home для головної сторінки або .main-content для внутрішніх. Визначте свій контейнер через DevTools (вкладка Elements → пошук за overflow: scroll або overflow: auto) і підставте в селектор:

1jQuery('body.home').animate(
2 { scrollTop: target.offset().top - 37 },
3 1000
4);

Проблема третя, хеш містить кирилицю або спецсимволи. location.hash видасть URL-кодовану послідовність замість читаного рядка, і jQuery-селектор $(myHash) не знайде елемент. Рішення, декодувати хеш через decodeURIComponent() перед підстановкою в селектор.

Відео: живий приклад скролу із зовнішнім переходом

Коротке демо від John Smith, плавний скрол до якоря через jQuery animate() на реальній сторінці з фіксованою шапкою та зовнішнім переходом.

⁉️🤔 Часті запитання

Скрипт працює всередині сторінки, але не працює при переході з іншого URL. Чому?

Тому що click-обробник не спрацьовує, кліку не було. Користувач перейшов за прямим посиланням, і браузер сам обробив хеш до виконання вашого JS. Лікується зв'язкою location.hash = '' + animate() + location.hash = myHash, саме в такій послідовності. Очищення хешу скасовує вбудований стрибок, анімація скролить плавно, повернення хешу зберігає правильний URL в адресному рядку.

Який відступ ставити під фіксовану шапку?

Точну висоту шапки плюс кілька пікселів повітря. Відкрийте DevTools (F12), виділіть елемент header вашого сайту, подивіться його height у вкладці Computed. Наприклад, шапка 80px, ставте 90 у коді. Якщо на мобільній версії шапка іншої висоти, додайте медіа-запит із динамічним розрахунком відступу.

jQuery все ще актуальний у 2026?

Так, особливо в екосистемі WordPress. jQuery залишається в ядрі WP, безліч плагінів і тем залежать від нього, і міграція всієї екосистеми на нативний JS займе роки. Для нових проєктів на чистому JavaScript плавний скрол робиться через element.scrollIntoView({ behavior: 'smooth' }) або scroll-behavior: smooth у CSS, рядків коду значно менше. Але якщо ви правите наявний WP-сайт, jQuery-рішення, як і раніше, надійне й доречне.

Чи можна обійтися без jQuery?

Можна й потрібно, якщо проєкт новий. Нативний аналог базового скрипта:

1document.addEventListener('DOMContentLoaded', () => {
2 const hash = window.location.hash;
3 if (hash) {
4 window.location.hash = '';
5 const target = document.querySelector(hash);
6 if (target) {
7 window.scrollTo({
8 top: target.getBoundingClientRect().top + window.pageYOffset - 110,
9 behavior: 'smooth'
10 });
11 }
12 window.location.hash = hash;
13 }
14});

Той самий принцип, зберегти хеш, очистити, проскролити, повернути. Різниця в тому, що behavior: 'smooth' використовує нативний CSS-рушій браузера, а не jQuery-таймер. Швидкість не налаштовується, зате код легший на 30 КБ (вага jQuery).

Чому $('html, body'), а не просто $('html')?

Firefox скролить html, Chrome використовує body, Safari залежить від версії. Вказання обох селекторів в animate() гарантує роботу на всіх браузерах: jQuery відправить анімацію туди, де вона реально застосується.

Чи варто городити jQuery заради плавного скролу в 2026

Якщо у вас WordPress-сайт або legacy-проєкт, де jQuery уже підключений, беріть скрипт зі статті й не ускладнюйте. Десять рядків коду, які працюють скрізь, з будь-яким браузером і не потребують поліфілів. Для нової верстки на чистому JavaScript, використовуйте нативний scroll-behavior: smooth або scrollIntoView(). Обидва варіанти дають плавний скрол, але jQuery-підхід залишає вам контроль над швидкістю, відступом і селектором скрол-контейнера, у нативній реалізації це потребуватиме окремих перевірок.

Головне правило, яке ми винесли з десятка проєктів: завжди очищайте хеш до скролу. Без цього трюку жоден обробник не переможе вбудовану поведінку браузера, і користувач так і буде стрибати ривком.