Предотвратить прокрутку нижней страницы маски

JavaScript CSS

Обзор сценария

Всплывающие окна — распространенный способ взаимодействия, а маска — обязательный элемент всплывающего окна, который используется для отделения страницы от блока всплывающего окна и временной блокировки взаимодействия страницы. Однако при скольжении по замаскированному элементу, когда вы скользите до конца содержимого и продолжаете скользить, страница в нижней части маски начнет прокручиваться.Очевидно, что это не тот эффект, который нам нужен, поэтому нам нужно предотвратить такое поведение.

Итак, как это остановить? Пожалуйста, посмотрите следующий анализ:

анализ случая

Вариант первый

  • При включенной маске добавьте стиль к телу:
overflow: hidden;
height: 100%;

В некоторых моделях вам также может понадобиться добавить стили в корневой узел:

overflow: hidden;
  • Когда маска отключена, вышеуказанные стили удаляются.

преимущество:Просто и удобно, просто добавьте стиль css, никакой сложной логики.

недостаток:Совместимость плохая, для ПК подходит, а мобильный терминал смущает. В некоторых моделях Android и сафари запретить прокрутку нижней страницы невозможно.

Если вам нужно применить его к мобильному терминалу, то вам может понадобиться второй вариант.

Вариант 2

Он заключается в использовании сенсорного события мобильного терминала для предотвращения поведения по умолчанию (здесь можно понять, что прокрутка страницы является поведением по умолчанию).

// node为蒙层容器dom节点
node.addEventListener('touchstart', e => {
  e.preventDefault()
}, false)

Просто и грубо, нижняя страница не может двигаться при прокрутке. Если ваш замаскированный контент не будет иметь полос прокрутки, то описанный выше метод идеален.

Однако больше всего меня пугает внезапная тишина эфира: если в маскируемом контенте есть полоса прокрутки, она уже не сможет двигаться. Поэтому нам нужно написать некоторую логику js, чтобы определить, следует ли блокировать поведение по умолчанию, и сложность значительно возрастает.

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

Совет: здесь я нашел небольшую хитрость, позволяющую пропустить большой объем кода. Одним движением, если содержимое оверлея можно прокручивать, содержимое оверлея будет прокручиваться.Даже если содержимое оверлея прокручено до конца, до тех пор, пока вы не отпускаете (это можно понимать как до того, как сработает событие `touchend`), содержимое страницы не изменится, если вы продолжите проводить пальцем по экрану. Оно будет прокручиваться. В это время, если вы отпустите и продолжите прокручивание, содержимое страницы будет прокручиваться. Используя этот маленький трюк, мы можем упростить и оптимизировать логику нашего кода.

Пример кода выглядит следующим образом:

<body>
  <div class="page">
    <!-- 这里多添加一些,直至出现滚动条 -->
    <p>页面</p>
    <p>页面</p>
    <button class="btn">打开蒙层</button>
    <p>页面</p>
  </div>
  <div class="container">
    <div class="layer"></div>
    <div class="content">
      <!-- 这里多添加一些,直至出现滚动条 -->
      <p>蒙层</p>
      <p>蒙层</p>
      <p>蒙层</p>
    </div>
  </div>
</body>
body {
  margin: 0;
  padding: 20px;
}

.btn {
  border: none;
  outline: none;
  font-size: inherit;
  border-radius: 4px;
  padding: 1em;
  width: 100%;
  margin: 1em 0;
  color: #fff;
  background-color: #ff5777;
}

.container {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 1001;
  display: none;
}

.layer {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 1;
  background-color: rgba(0, 0, 0, .3);
}

.content {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 50%;
  z-index: 2;
  background-color: #f6f6f6;
  overflow-y: auto;
}
const btnNode = document.querySelector('.btn')
const containerNode = document.querySelector('.container')
const layerNode = document.querySelector('.layer')
const contentNode = document.querySelector('.content')
let startY = 0 // 记录开始滑动的坐标,用于判断滑动方向
let status = 0 // 0:未开始,1:已开始,2:滑动中

// 打开蒙层
btnNode.addEventListener('click', () => {
  containerNode.style.display = 'block'
}, false)

// 蒙层部分始终阻止默认行为
layerNode.addEventListener('touchstart', e => {
  e.preventDefault()
}, false)

// 核心部分
contentNode.addEventListener('touchstart', e => {
  status = 1
  startY = e.targetTouches[0].pageY
}, false)

contentNode.addEventListener('touchmove', e => {
  // 判定一次就够了
  if (status !== 1) return

  status = 2

  let t = e.target || e.srcElement
  let py = e.targetTouches[0].pageY
  let ch = t.clientHeight // 内容可视高度
  let sh = t.scrollHeight // 内容滚动高度
  let st = t.scrollTop // 当前滚动高度

  // 已经到头部尽头了还要向上滑动,阻止它
  if (st === 0 && startY < py) {
    e.preventDefault()
  }

  // 已经到低部尽头了还要向下滑动,阻止它
  if ((st === sh - ch) && startY > py) {
    e.preventDefault()
  }
}, false)

contentNode.addEventListener('touchend', e => {
  status = 0
}, false)

Хотя проблема решена, оглядываясь назад, можно сказать, что сложность и объем кода явно увеличились по градиенту. Исходя из принципа простоты и удобства, можем ли мы изучить другие решения?

Поскольку определение события касания более сложное, почему бы не выйти за рамки этого правила, найти другой способ и изучить более подходящее решение. Итак, у нас есть третий план.

третье решение

Если говорить о моем размышлении, так как мы хотим предотвратить прокрутку страницы, почему бы не исправить это в области просмотра (т.position: fixed), чтобы его нельзя было прокрутить, и отпустите, когда маска будет закрыта. Конечно, есть еще некоторые детали, которые нужно учитывать.После того, как страница будет прикреплена к окну, содержимое вернется наверх.Здесь нам нужно записать и синхронизировать верхнее значение.

Образец кода:

let bodyEl = document.body
let top = 0

function stopBodyScroll (isFixed) {
  if (isFixed) {
    top = window.scrollY

    bodyEl.style.position = 'fixed'
    bodyEl.style.top = -top + 'px'
  } else {
    bodyEl.style.position = ''
    bodyEl.style.top = ''

    window.scrollTo(0, top) // 回到原先的top
  }
}

Резюме мыслей

  • Если сценарий приложения — ПК, мы рекомендуем решение 1, которое действительно не слишком удобно.
  • Если сценарий приложения — h5, вы можете использовать решение 2, но я предлагаю вам использовать решение 3.
  • Если сценарий приложения — вся платформа, то нельзя упускать третий вариант

На этом статья подходит к концу, здесь я настоятельно рекомендую третье решение, потому что оно простое, удобное и имеет хорошую совместимость, после упаковки его можно использовать постоянно.