Предотвратить прокрутку тела, но разрешить прокрутку наложения - PullRequest
382 голосов
/ 14 февраля 2012

Я искал решение типа "лайтбокс", которое позволяет это, но еще не нашло его (пожалуйста, предложите, если знаете что-либо).

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

Я попытался создать это только с помощью CSS (, то есть наложение div поверх всей страницы и тела с overflow: hidden), но это не мешает прокручивать div .

Как предотвратить прокрутку тела / страницы, но продолжать прокрутку внутри полноэкранного контейнера?

Ответы [ 18 ]

571 голосов
/ 14 февраля 2012

Theory

Глядя на текущую реализацию сайта интереса (она может измениться в будущем), когда вы открываете оверлей, класс noscroll применяется к элементу body и устанавливается overflow: hidden, таким образом, body больше не прокручивается.

Наложение (создается на лету или уже внутри страницы и отображается через display: block, без разницы) имеет position : fixed и overflow-y: scroll, с top, left, right и bottom свойства установлены на 0: этот стиль заставляет оверлей заполнять весь видовой экран.

Вместо div внутри оверлея используется только position: static, тогда вертикальная полоса прокрутки, которую вы видите, связана с этим элементом. В результате содержимое прокручивается, но наложение остается фиксированным.

Когда вы закрываете зум, вы скрываете оверлей (через display: none), а затем можете полностью удалить его с помощью javascript (или только содержимого внутри, вам решать, как его вставить).

В качестве последнего шага вы также должны удалить класс noscroll в body (чтобы свойство переполнения вернулось к своему начальному значению)


Код

Пример Codepen

(работает путем изменения атрибута оверлея aria-hidden, чтобы отобразить и скрыть его, а также увеличить его доступность).

Разметка
(кнопка открытия)

<button type="button" class="open-overlay">OPEN LAYER</button>

(кнопка наложения и закрытия)

<section class="overlay" aria-hidden="true">
  <div>
    <h2>Hello, I'm the overlayer</h2>
    ...   
    <button type="button" class="close-overlay">CLOSE LAYER</button>
  </div>
</section>

CSS

.noscroll { 
  overflow: hidden;
}

.overlay { 
   position: fixed; 
   overflow-y: scroll;
   top: 0; right: 0; bottom: 0; left: 0; }

[aria-hidden="true"]  { display: none; }
[aria-hidden="false"] { display: block; }

Javascript (vanilla-JS)

var body = document.body,
    overlay = document.querySelector('.overlay'),
    overlayBtts = document.querySelectorAll('button[class$="overlay"]');

[].forEach.call(overlayBtts, function(btt) {

  btt.addEventListener('click', function() { 

     /* Detect the button class name */
     var overlayOpen = this.className === 'open-overlay';

     /* Toggle the aria-hidden state on the overlay and the 
        no-scroll class on the body */
     overlay.setAttribute('aria-hidden', !overlayOpen);
     body.classList.toggle('noscroll', overlayOpen);

     /* On some mobile browser when the overlay was previously
        opened and scrolled, if you open it again it doesn't 
        reset its scrollTop property */
     overlay.scrollTop = 0;

  }, false);

});

Наконец, вот еще один пример, в котором наложение открывается с эффектом постепенного появления CSS transition, примененным к свойству opacity. Также padding-right применяется, чтобы избежать перекомпоновки основного текста, когда полоса прокрутки исчезает.

Пример Codepen (затухание)

CSS

.noscroll { overflow: hidden; }

@media (min-device-width: 1025px) {
    /* not strictly necessary, just an experiment for 
       this specific example and couldn't be necessary 
       at all on some browser */
    .noscroll { 
        padding-right: 15px;
    }
}

.overlay { 
     position: fixed; 
     overflow-y: scroll;
     top: 0; left: 0; right: 0; bottom: 0;
}

[aria-hidden="true"] {    
    transition: opacity 1s, z-index 0s 1s;
    width: 100vw;
    z-index: -1; 
    opacity: 0;  
}

[aria-hidden="false"] {  
    transition: opacity 1s;
    width: 100%;
    z-index: 1;  
    opacity: 1; 
}
70 голосов
/ 26 сентября 2013

Если вы хотите предотвратить чрезмерную прокрутку на ios, вы можете добавить фиксированную позицию в ваш класс .noscroll

body.noscroll{
    position:fixed;
    overflow:hidden;
}
38 голосов
/ 26 июня 2015

Не используйте overflow: hidden; на body. Он автоматически прокручивает все наверх. Там нет необходимости для JavaScript либо. Используйте overflow: auto;. Это решение работает даже с мобильным Safari:

Структура HTML

<div class="overlay">
    <div class="overlay-content"></div>
</div>

<div class="background-content">
    lengthy content here
</div>

стайлинг

.overlay{
    position: fixed;
    top: 0px;
    left: 0px;
    right: 0px;
    bottom: 0px;
    background-color: rgba(0, 0, 0, 0.8);

    .overlay-content {
        height: 100%;
        overflow: scroll;
    }
}

.background-content{
    height: 100%;
    overflow: auto;
}

См. Демо здесь и исходный код здесь .

Обновление:

Для людей, которые хотят, чтобы клавиша пробела на клавиатуре работала вверх / вниз: вам нужно сфокусироваться на оверлее, например, щелкнув по нему, или вручную JS сфокусироваться на нем, прежде чем эта часть div ответит на клавиатуру , То же самое с тем, когда оверлей «выключен», так как он просто перемещает оверлей в сторону. В противном случае для браузера это всего лишь два обычных div с, и он не знал бы, почему он должен фокусироваться на любом из них.

30 голосов
/ 22 февраля 2014

Стоит отметить, что иногда добавление «overflow: hidden» к тегу body не помогает. В этих случаях вам также необходимо добавить свойство в тег html.

html, body {
    overflow: hidden;
}
28 голосов
/ 23 февраля 2018

overscroll-behavior Свойство css позволяет переопределить поведение прокрутки переполнения браузера по умолчанию при достижении верха / низа содержимого.

Просто добавьте следующие стили в оверлей:

.overlay {
   overscroll-behavior: contain;
   ...
}

Codepen demo

В настоящее время работает в Chrome, Firefox и IE ( caniuse )

Подробнее см. статья для разработчиков Google .

27 голосов
/ 21 июля 2017

В большинстве решений есть проблема, заключающаяся в том, что они не сохраняют позицию прокрутки, поэтому я взглянул на то, как это делает Facebook.В дополнение к настройке нижележащего содержимого на position: fixed они также динамически устанавливают верхнюю часть для сохранения позиции прокрутки:

scrollPosition = window.pageYOffset;
mainEl.style.top = -scrollPosition + 'px';

Затем, когда вы снова удалите оверлей, вам необходимо сбросить положение прокрутки:

window.scrollTo(0, scrollPosition);

Я создал небольшой пример, чтобы продемонстрировать это решение

let overlayShown = false;
let scrollPosition = 0;

document.querySelector('.toggle').addEventListener('click', function() {
  if (overlayShown) {
		showOverlay();
  } else {
    removeOverlay();
  }
  overlayShown = !overlayShown;
});

function showOverlay() {
    scrollPosition = window.pageYOffset;
  	const mainEl = document.querySelector('.main-content');
    mainEl.style.top = -scrollPosition + 'px';
  	document.body.classList.add('show-overlay');
}

function removeOverlay() {
		document.body.classList.remove('show-overlay');
  	window.scrollTo(0, scrollPosition);
    const mainEl = document.querySelector('.main-content');
    mainEl.style.top = 0;
}
.main-content {
  background-image: repeating-linear-gradient( lime, blue 103px);
  width: 100%;
  height: 200vh;
}

.show-overlay .main-content {
  position: fixed;
  left: 0;
  right: 0;
  overflow-y: scroll; /* render disabled scroll bar to keep the same width */
}

.overlay {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.3);
  overflow: auto;
}

.show-overlay .overlay {
  display: block;
}

.overlay-content {
  margin: 50px;
  background-image: repeating-linear-gradient( grey, grey 20px, black 20px, black 40px);
  height: 120vh;
}

.toggle {
  position: fixed;
  top: 5px;
  left: 15px;
  padding: 10px;
  background: red;
}

/* reset CSS */
body {
  margin: 0;
}
<main class="main-content"></main>

  <div class="overlay">
    <div class="overlay-content"></div>
  </div>
  
  <button class="toggle">Overlay</button>
7 голосов
/ 15 февраля 2017

Выбранный ответ правильный, но имеет некоторые ограничения:

  • Сверхтвердый "взмах" пальцем все равно будет прокручиваться <body> на заднем плане
  • Открытие виртуальной клавиатуры нажатием <input> в модальном режиме приведет к тому, что все будущие прокрутки будут <body>

У меня нет решения для первой проблемы, но я хотел пролить свет на вторую. Смущает, что Bootstrap раньше документировал проблему с клавиатурой, но они утверждали, что она исправлена, ссылаясь на http://output.jsbin.com/cacido/quiet в качестве примера исправления.

Действительно, этот пример отлично работает на iOS с моими тестами. Тем не менее, обновление до последней версии Bootstrap (v4) ломает его.

В попытке выяснить, в чем разница между ними, я сократил тестовый набор, чтобы он больше не зависел от Bootstrap, http://codepen.io/WestonThayer/pen/bgZxBG.

Решающие факторы странные. Чтобы избежать проблемы с клавиатурой, требуется, чтобы background-color не был установлен в корне <div>, содержащем модальные и , содержимое модала должно быть вложено в другой <div>, который может есть background-color набор.

Чтобы проверить это, раскомментируйте следующую строку в примере Codepen:

.modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2;
  display: none;
  overflow: hidden;
  -webkit-overflow-scrolling: touch;
  /* UNCOMMENT TO BREAK */
/*   background-color: white; */
}
7 голосов
/ 17 ноября 2013

Вообще говоря, если вы хотите, чтобы родитель (тело в данном случае) не давал ему прокручиваться при прокрутке дочернего элемента (в данном случае оверлея), то сделайте дочернего элемента родителем, чтобы предотвратить событие прокрутки. пузырится до родителя. В случае если родитель является телом, для этого требуется дополнительный элемент переноса:

<div id="content">
</div>
<div id="overlay">
</div>

См. Прокрутите определенное содержимое DIV с помощью главной полосы прокрутки браузера , чтобы увидеть, как оно работает.

4 голосов
/ 25 августа 2016

Для сенсорных устройств попробуйте добавить прозрачный div шириной 1px, 101vh min-height в оболочку наложения. Затем добавьте -webkit-overflow-scrolling:touch; overflow-y: auto; в оболочку. Это обманывает мобильное сафари, заставляя думать, что оверлей можно прокручивать, перехватывая событие касания от тела.

Вот пример страницы. Открыть на мобильном сафари: http://www.originalfunction.com/overlay.html

https://gist.github.com/YarGnawh/90e0647f21b5fa78d2f678909673507f

3 голосов
/ 10 февраля 2016

Я нашел этот вопрос, пытаясь решить проблему с моей страницей на Ipad и Iphone - тело прокручивалось, когда я отображал фиксированный div как всплывающее окно с изображением.

Некоторые ответы хорошие, но ни один из них не решил мою проблему. Я нашел следующее сообщение в блоге от Christoffer Pettersson Представленное там решение помогло решить проблему с iOS-устройствами и помогло решить проблему с прокруткой.

Шесть вещей, которые я узнал о прокрутке резиновой ленты в iOS Safari

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

"Чтобы отключить возможность пользователю прокручивать фоновую страницу, пока открыто" меню ", можно контролировать, какие элементы следует прокручивать или нет, применяя некоторые JavaScript и класс CSS.

На основании этого ответа Stackoverflow вы можете контролировать, что элементы с отключенной прокруткой не должны выполнить их действие прокрутки по умолчанию при срабатывании события touchmove. "

 document.ontouchmove = function ( event ) {

    var isTouchMoveAllowed = true, target = event.target;

    while ( target !== null ) {
        if ( target.classList && target.classList.contains( 'disable-scrolling' ) ) {
            isTouchMoveAllowed = false;
            break;
        }
        target = target.parentNode;
    }

    if ( !isTouchMoveAllowed ) {
        event.preventDefault();
    }
};

А затем поместите класс disable-scrolling на страницу div:

<div class="page disable-scrolling">
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...