Модальные окна, которые мы заслужили

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Начнем


В разных критериях и манифестах качества есть такой пункт, как блокировка скролла страницы, когда открыто модальное окно. Модальное окно подразумевает под собой взаимодействие только с ним.

Многие зададут вопрос: «Почему не использовать готовые решения?». Проблема в том, что этот функционал либо вообще не реализован, либо сделан очень плохо. Можно использовать самое простое и плохое решение:

.body { overflow: hidden;}

Здесь мы получаем легкое подергивание страницы. Код. Стоит отметить: проблема встречается только там, где скролл занимает место на странице, например, в Safari такой проблемы нет.

Анимация проблемы(Gif)


Некоторые решения на просторах 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 несколько раз.

Анимация проблемы(Gif)


На просторах интернета нет простого решения этой проблемы, а писать свой костыль велосипед не очень рационально, когда есть библиотека 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>
Источник: https://habr.com/ru/post/483114/


Интересные статьи

Интересные статьи

Привет, Хабр! Меня зовут Саша Козлов, я занимаюсь разработкой инфраструктуры и системным администрированием в Авито последние три с половиной года. Я расскажу, как мы масшта...
Последние несколько лет мы наблюдаем множество изменений в мире веб технологий. В 2020 году, я хочу обратить ваше внимание на два основных тренда/цели которые есть у веб комьюнити: расширяемост...
В прошлом году специалисты по информационной безопасности и журналисты выяснили, что Facebook использует для таргетированной рекламы телефонный номер, который пользователь вводит для двухфакторно...
LINQ вошел в .NET как новый мощный язык манипуляции с данными. LINQ to SQL как часть его позволяет достаточно удобно общаться с СУБД с помощью например Entity Framework. Однако, достаточно часто ...
Если Вы используете в своих проектах инфоблоки 2.0 и таблицы InnoDB, то есть шанс в один прекрасный момент столкнуться с ошибкой MySQL «SQL Error (1118): Row size too large. The maximum row si...