Начнем
В разных критериях и манифестах качества есть такой пункт, как блокировка скролла страницы, когда открыто модальное окно. Модальное окно подразумевает под собой взаимодействие только с ним.
Многие зададут вопрос: «Почему не использовать готовые решения?». Проблема в том, что этот функционал либо вообще не реализован, либо сделан очень плохо. Можно использовать самое простое и плохое решение:
.body { overflow: hidden;}
Здесь мы получаем легкое подергивание страницы. Код. Стоит отметить: проблема встречается только там, где скролл занимает место на странице, например, в Safari такой проблемы нет.

Некоторые решения на просторах npm предлагают замену скролла отступом или блоком, который стилизован под скролл (простая серая полоска), много магии, но проблему они не исправляют.
Немного подправим начальный код, и все работает:
.body {
position:fixed;
overflow-y: scroll;
}
Теперь появилась другая проблема: страница прыгает вверх. Код. Решение через js простое: при открытии страницы берем значение, на которое проскроллена страница, и задаем это значение свойству top тега <body> Код.
function getBodyScrollTop() {
return self.pageYOffset || (document.documentElement && document.documentElement.ScrollTop) || (document.body && document.body.scrollTop);
}
openModalButton.addEventListener('click', e => {
e.preventDefault()
body.dataset.scrollY = getBodyScrollTop() // сохраним значение скролла
body.style.top = `-${body.dataset.scrollY}px`
modal.classList.add('modal--open')
body.classList.add('body-lock')
})
closeModalButton.addEventListener('click', e => {
e.preventDefault()
modal.classList.remove('modal--open')
body.classList.remove('body-lock')
window.scrollTo(0, body.dataset.scrollY) // берем значение, которое сохранили
})
Иногда на сайте может встречаться страничка, у которой мало контента и нет скролла, но при открытии модального окна наш код добавляет его. Немного доработаем скролл. Готовое решение.
function existVerticalScroll() {
return document.body.offsetHeight > window.innerHeight
}
openModalButton.addEventListener('click', e => {
e.preventDefault()
body.dataset.scrollY = getBodyScrollTop()
modal.classList.add('modal--open')
if(existVerticalScroll()) { // новая строка
body.classList.add('body-lock')
body.style.top = `-${body.dataset.scrollY}px`
}
})
closeModalButton.addEventListener('click', e => {
e.preventDefault()
modal.classList.remove('modal--open')
if(existVerticalScroll()) { // новая строка
body.classList.remove('body-lock')
window.scrollTo(0,body.dataset.scrollY)
}
})
Доступность
Если о ней не подумать, будет возможное перемещение с помощью таба вне модального окна. На gif только нажатие Tab несколько раз.

На просторах интернета нет простого решения этой проблемы, а писать свой костыль велосипед не очень рационально, когда есть библиотека focus-trap.js. Здесь автор более детально раскрыл тему библиотек и их проблем.
// Не забудьте подключить плагин
const modalFocusTrap = createFocusTrap(".modal"); // инициализируем плагин для модального окна
openModalButton.addEventListener('click', e => {
e.preventDefault()
body.dataset.scrollY = getBodyScrollTop()
modal.classList.add('modal--open')
modalFocusTrap.activate(); // новая строка. Активируем плагин
if(existVerticalScroll()) {
body.classList.add('body-lock')
body.style.top = `-${body.dataset.scrollY}px`
}
})
closeModalButton.addEventListener('click', e => {
e.preventDefault()
modal.classList.remove('modal--open')
modalFocusTrap.deactivate(); // новая строка. Отключаем плагин
if(existVerticalScroll()) {
body.classList.remove('body-lock')
window.scrollTo(0,body.dataset.scrollY)
}
})
Итоги
Даже если на странице несколько модальных окон, весь код можно обернуть в одну функцию и передавать название класса модального окна.
function initModal(className) {
// код, что мы писали выше для одной модалки
}
initModal('modalName1')
initModal('modalName2')
Проблема на первый взгляд несложная, но как много неочевидных моментов. Думаю, эта статья поможет вам делать более качественные модальные окна. Готовый код
Полезные материалы
- Focusable Elements — Browser Compatibility Table
- Оставил для новичков, кто встретил dataset в первый раз
- О теге <dialog>
Автор: Денис