
🖱 Плавный скролл к якорю: CSS, JavaScript и jQuery — три рабочих подхода
Пользователь кликает по ссылке с якорем, и страница мгновенно перепрыгивает в нужное место. Ни анимации, ни плавности, ни понимания, где ты оказался. Рваный переход сбивает с толку, особенно на длинных лендингах и в документации.
Добавить плавный скролл к якорю можно за 5 минут. Но дьявол в деталях: ссылка может вести с внешнего сайта, в URL уже есть хеш, а сверху висит фиксированный хедер, который перекрывает якорь. Обычный scroll-behavior: smooth этих нюансов не решает.
Ниже, три рабочих подхода: чистый CSS, нативный JavaScript и jQuery. Каждый с кодом, который можно забрать и вставить в проект. Плюс отдельно разбираем внешние переходы, чтобы скролл срабатывал, даже когда пользователь приходит с другой страницы или из поисковой выдачи.
💡 Быстрый обзор:
- Включаем плавный скролл одной CSS-строкой, без JavaScript вообще
- Добавляем отступ под фиксированный хедер через
scroll-margin-top - Пишем нативный JS с
scrollIntoView, для внешних переходов и тонкой настройки - Разбираем jQuery-вариант с
animate(), когда нужна кастомная скорость и контроль - Обрабатываем внешний хеш: страница загрузилась, якорь в URL, и плавно едем к цели
Зачем вообще плавный скролл
Резкий прыжок к якорю дезориентирует. Пользователь теряет контекст: где я был, куда попал, что пропустил? Плавная прокрутка сохраняет пространственную ориентацию, глазу есть за что зацепиться, пока страница движется.
С точки зрения восприятия сайт с плавным скроллом выглядит отполированным. Особенно это заметно на длинных страницах: лендинги, документация, FAQ-разделы, оглавления статей. Пользователь кликает «Тарифы» в меню, и страница не дёргается, а мягко сползает к блоку с ценами.
Технически задача распадается на две: скролл при клике по внутренней ссылке (пользователь уже на странице) и скролл при внешнем переходе (в URL сразу есть #anchor). Во втором случае браузер по умолчанию прыгает к якорю мгновенно ещё до полной загрузки, и это надо перехватывать.
CSS scroll-behavior, одна строка вместо скрипта
Самый простой и современный способ. Браузеры научились плавному скроллу нативно, достаточно одного CSS-правила:
1 html { 2 scroll-behavior: smooth; 3 }
Всё. Любой переход по якорной ссылке становится плавным. Никакого JavaScript, никаких библиотек.
Поддержка, все современные браузеры с 2022 года: Chrome 61+, Firefox 36+, Safari 15.4+, Edge 79+. Единственное исключение, IE11, но его доля трафика стремится к нулю.
Фиксированный хедер: scroll-margin-top
Если над контентом висит фиксированная шапка (sticky header), якорь после скролла окажется под ней. Решается одной строкой:
1 h2, h3, [id] { 2 scroll-margin-top: 80px; /* высота хедера + отступ */ 3 }
Браузер сам добавит отступ при скролле к целевому элементу. Раньше для этого писали костыли с padding-top и отрицательным margin, теперь не нужно.
Ограничения чистого CSS
У CSS-подхода нет контроля над скоростью анимации и функцией плавности (easing). Браузер использует встроенную кривую, обычно ease-in-out. Если нужна кастомная скорость или нелинейная анимация, переходим к JavaScript.
Нативный JavaScript: scrollIntoView
Когда одного CSS недостаточно, на помощь приходит метод scrollIntoView() с опцией behavior: 'smooth':
1 document.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 });
Этот код перехватывает клик по всем ссылкам с #, отменяет стандартный переход и запускает плавный скролл. Работает без jQuery и без дополнительных библиотек.
Плюс подхода, можно программно управлять: менять скорость через CSS scroll-behavior на <html>, добавлять условия (например, исключать определённые ссылки), комбинировать с scroll-margin-top для хедера.
Минус, scrollIntoView не даёт задать скорость анимации напрямую. Для кастомного времени скролла (скажем, 800 мс вместо браузерных ~500 мс) нужен либо jQuery, либо ручной requestAnimationFrame.
jQuery animate, полный контроль над анимацией
Если проект уже использует jQuery или нужна тонкая настройка (скорость, отступ под хедер, фильтрация ссылок), вот актуальный скрипт. Ниже адаптированная версия классического подхода, переписанная под современный jQuery:
1 jQuery(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 });
Что здесь поменялось относительно старых версий, которые кочуют по блогам:
.bind()заменён на.on(),.bind()объявлен устаревшим с jQuery 3.0 и будет удалён в jQuery 4.0.- Селектор
'a:not(.spu-clickable)[href*="#"]:not([href="#"])'упрощён, специфичный класс.spu-clickableотносился к конкретному плагину (Popups by OptinMonster) и в общем случае не нужен. jQuery(window).bind("load", ...)убран, вторая часть скрипта для внешнего хеша теперь живёт в$(document).ready()и не требует отдельного событияload.- Добавлена задержка
setTimeout(..., 100), чтобы DOM гарантированно отрисовался до расчёта позиции якоря (актуально для страниц с ленивой загрузкой изображений и динамическим контентом).
Куда вставлять код
Три варианта на выбор:
- Через файл темы. Добавьте скрипт в
functions.phpчерезwp_enqueue_script(), стандартный способ для дочерней темы. - Плагин Code Snippets. Вставьте код как новый сниппет с автозапуском, не потеряется при обновлении темы.
- Кастомный JS-плагин. Если на сайте уже используется плагин для вставки произвольного JavaScript (например, WPCode), добавьте код туда.
Обработка внешнего перехода с хешем, детальный разбор
Самая хитрая часть задачи. Пользователь переходит по ссылке https://site.com/page/#pricing с другого сайта или из поиска. Браузер по умолчанию мгновенно прыгает к #pricing, как только элемент появляется в DOM, без анимации и часто до полной загрузки страницы.
jQuery-скрипт выше решает это двумя шагами:
- Определяет наличие хеша через
window.location.hash. Если хеш есть, читает его значение (без#). - Ищет цель и скроллит.
$('#' + hash)находит элемент,setTimeoutдаёт странице отрисоваться,animate()ведёт к цели с отступом 80 пикселей.
Задержка в 100 миллисекунд, эмпирическая. На быстрых страницах хватает и 50 мс, на тяжёлых (лендинги с фоновым видео, картами, графиками) лучше увеличить до 200-300 мс. Альтернатива, дождаться события window.load вместо document.ready, но тогда скролл произойдёт только после загрузки всех изображений, что может затянуться.
Что делать, если якорь не находит цель
Иногда хеш в URL указывает на несуществующий элемент. Например, пользователь опечатался или страницу поменяли. В этом случае ни CSS, ни JS-подход не упадут с ошибкой, просто ничего не произойдёт. Браузер проигнорирует несуществующий якорь. Плавный скролл тоже молча отработает (условие if (target.length) не пустит дальше).
Сравнение трёх подходов
Критерий | CSS scroll-behavior | JS scrollIntoView | jQuery animate |
|---|---|---|---|
Сложность внедрения | 1 строка CSS | 10-15 строк JS | 25-30 строк JS + jQuery |
Контроль скорости | Нет | Нет (браузерный) | Да (мс) |
Внешний хеш | Да, нативно | Требует доп. кода | Требует доп. кода |
Отступ под хедер |
|
| Ручной в |
Зависимости | Нет | Нет | jQuery 3.x |
Поддержка браузерами | 96%+ | 96%+ | 99%+ (с jQuery) |
Для большинства современных проектов оптимален CSS-подход, одна строка, ноль зависимостей, нативная производительность. Если нужен отступ под хедер, добивается scroll-margin-top. Если нужна кастомная скорость или поддержка древних браузеров, jQuery с animate().
Ниже видео с живой демонстрацией всех трёх подходов, от CSS до jQuery:
⁉️🤔 Частые вопросы
Почему плавный скролл не работает на мобильных устройствах?
В iOS Safari до версии 15.4
scroll-behavior: smoothне поддерживался. Сейчас поддержка есть (Safari 15.4+, март 2022). Если скролл не работает на старых iPhone, добавьте JavaScript-фолбек сscrollIntoViewилиrequestAnimationFrame. Также проверьте, не блокирует ли скроллoverflow: hiddenнаbody, некоторые мобильные меню ставят его при открытии. По состоянию на июнь 2026 года глобальная поддержкаscroll-behavior, 96,3% браузеров (данные Can I Use).
Как сделать плавный скролл не к якорю, а к произвольному элементу по клику на кнопку?
Дайте элементу
idи используйте тот же JavaScript, но привязанный к кнопке, а не к якорной ссылке. Например, кнопка «Вернуться наверх»:document.querySelector('#back-to-top').addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })). Для jQuery-варианта:$('#back-to-top').on('click', () => $('html, body').animate({ scrollTop: 0 }, 600)). Никаких якорей не нужно, скроллимся к координате.
Нужно ли подключать jQuery только ради плавного скролла в 2026 году?
Нет. jQuery тянет около 87 КБ в min-версии (30 КБ gzip). Если на сайте больше нигде не используется jQuery, подключать его ради одного скролла нерационально. Берите CSS-подход с
scroll-behavior: smoothили нативныйscrollIntoView. jQuery оправдан, только если сайт уже собран на нём (темы на jQuery, legacy-проекты) и вы хотите добавить скролл в существующую кодовую базу.
Можно ли анимировать скролл с разной скоростью, сначала быстро, потом медленно?
Да, через
$.animate()с jQuery UI или черезrequestAnimationFrameс функцией плавности (easing). Стандартный jQueryanimate()использует линейную или swing-функцию. Для нестандартных кривых подключайтеjquery.easingили пишите свой цикл наrequestAnimationFrameс кастомной функцией плавности, например, easeInOutQuad. Это даёт полный контроль над скоростью, траекторией и не требует внешних библиотек.
Как проверить, что скролл работает при внешнем переходе с хешем?
Откройте новую вкладку, вставьте URL с якорем (например,
https://вашсайт.ру/page/#contacts) и нажмите Enter. Страница должна загрузиться и плавно проскроллить к блоку. Второй вариант, в консоли браузера на открытой странице выполнитьlocation.hash = '#contacts'. Если скролл плавный, скрипт работает. Если страница дёрнулась мгновенно, обработчик внешнего хеша не настроен.
Какой подход выбрать под вашу задачу
Если сайт современный, без jQuery, **CSS **scroll-behavior: smooth закрывает подавляющее большинство сценариев. Одна строка, мгновенный результат. Добавьте scroll-margin-top под хедер, и готово.
Если сайт на WordPress с jQuery в теме, **jQuery-скрипт с **animate(). Даёт контроль над скоростью и отступом, обрабатывает внешние переходы, не конфликтует с другими плагинами.
Если пишете с нуля и не хотите тянуть библиотеку, **нативный **scrollIntoView() с обработчиком внешнего хеша. Золотая середина: чисто, быстро, без зависимостей.
Берите код из статьи, вставляйте в проект и проверяйте внешний переход, это единственный кейс, который легко пропустить при тестировании.



