
🔧 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 рядків. Він вирішує рівно одне завдання: плавно проскролити до якоря, коли користувач прийшов з іншої сторінки або за прямим посиланням із хешем.
1 jQuery(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 = '' не спрацьовує: браузер встигає стрибнути до очищення хешу. Тоді підключаємо другий варіант, він важчий, але надійніший.
1 jQuery(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 22 jQuery(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, замініть на:
1 jQuery(window).on('load', function () { 2 // код здесь 3 });
Різниця в синтаксисі, поведінка ідентична.
Тонке налаштування: відступи, селектори та підводні камені
Скрипт робочий, але кожна верстка індивідуальна. Три частих доопрацювання, які рятували на реальних проєктах.
Проблема перша, offset().top повертає не ті координати. Таке буває, коли в батьківських блоків задано CSS-властивість transform, filter або will-change, вони створюють новий контекст позиціонування, і offset() рахує відносно нього, а не документа. Рішення, варіант з offset({top: -0}):
1 jQuery('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) і підставте в селектор:
1 jQuery('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?
Можна й потрібно, якщо проєкт новий. Нативний аналог базового скрипта:
1 document.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-підхід залишає вам контроль над швидкістю, відступом і селектором скрол-контейнера, у нативній реалізації це потребуватиме окремих перевірок.
Головне правило, яке ми винесли з десятка проєктів: завжди очищайте хеш до скролу. Без цього трюку жоден обробник не переможе вбудовану поведінку браузера, і користувач так і буде стрибати ривком.



