Предотвратить прокрутку тела, но разрешить прокрутку наложения


446

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

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

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

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


разве это не просто плагин "overlay", как hundrets, использующий положение, зафиксированное с помощью overflow-y scroll?
ggzone

3
Ссылка на Pinterest не помогает, так как ее содержимое находится за стеной входа в систему.
2540625

4
Обновление 2017: даже если вы войдете в Pinterest, вы обнаружите, что эффект наложения, описанный в OP, больше не существует - вместо этого, когда вы нажимаете на изображение, вы просто переходите на обычную страницу с большой версией изображения.
Аннабель

Ответы:


624

теория

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

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

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 (ваниль-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применяется a, чтобы избежать перекомпоновки основного текста, когда полоса прокрутки исчезает.

Пример 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; 
}

6
Это правильный ответ. Кто-то должен поставить галочку, так как это правильно.
Scoota P

64
Это абсолютно правильно, но будьте осторожны, это не работает в мобильном сафари. Фон будет продолжать прокручиваться, и ваше наложение будет исправлено.
a10s

20
Работает нормально. Контролируемый контейнер видового экрана divдолжен иметь стиль CSS position:fixedи иметь возможность вертикальной прокрутки. Я успешно использовал overflow-y:auto;и для iOS прокрутки импульса / инерции, я добавил -webkit-overflow-scrolling:touch;в CSS. Я использовал display:block;, width:100%;и height:100%;CSS , чтобы иметь полную страницу просмотра.
Slink

10
Простите, я не понимаю. Настройка переполнения тела: скрытый не отключает прокрутку эластичного тела на моем iPad iOS7. Поэтому мне пришлось добавить в свой js: document.body.addEventListener ('touchmove', function (event) {event.preventDefault ();}, false); который SADLY отключает прокручивание всех элементов. Не нашли никакого решения до сих пор (без дополнительных плагинов)
Гаравани

22
Если пользователь уже прокрутил тело вниз до низа, когда наложение активировано, это заставит тело подпрыгнуть вверх, когда вы установите переполнение: скрытый; Есть ли способ обойти это?
Бьорн Андерссон

75

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

body.noscroll{
    position:fixed;
    overflow:hidden;
}

106
При таком решении из-за фиксированного положения тело автоматически прокручивается до верхней части вашего контента. Это может беспокоить ваших пользователей.
Ци

5
Другая проблема с использованием position:fixedзаключается в том, что он изменял размеры моего основного тела. Возможно, это противоречило другим CSS, но overflow:hiddenэто все, что было нужно
Dex

3
это нарушает фокусировку при
переходе

@ Atav32 Звучит как отдельная проблема, позиция и переполнение не должны влиять на порядок табуляции.
am80l

@ am80l извините, мой предыдущий комментарий был очень расплывчатым: порядок вкладок работает, но экран
трясется при переходе

44

Не используйте 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и он не знает, почему он должен фокусироваться на каком-либо из них.


5
классное решение, но тогда мне нужно обернуть весь мой контент в div, что я не собирался делать ...
Джейкоб Раккуя

2
Это идеальное решение. Если у кого-то возникли проблемы с этим, вам может понадобиться добавить html, body { height: 100%; }(как в демоверсии), чтобы это работало правильно.
Джон

4
@ user18490 угловая / материальная часть не имеет ничего общего с тем, что это решение работает.
Люсия

10
Это нарушает UX мобильных устройств несколькими способами (например, скрытие URL-панели, доступность избыточной прокрутки, ...), даже пробел для прокрутки на рабочих столах.
бесконечно

2
Это более надежный вариант, но он немного усложняет ситуацию. Например, PageUp и PageDown не будут работать при обновлении. все, что использует .offset()значения для расчета,
испортится и

42

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

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

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

Codepen демо

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

Для более подробной информации обратитесь к статье разработчиков Google .


4
Я прошел весь путь, чтобы сказать вам, что это работает над последними версиями Chrome, Mozilla и Opera. Приятно провести время!
Wannabe JavaGeek

3
Кто-то должен добавить награду за это. Это правильное решение. JavaScript не требуется. :)
joseph.l.hunsaker

9
Проблема с этим решением состоит в том, что оно работает, только если оверлей можно прокручивать. Если у вас есть модал, и все это помещается на экране, поэтому внутри нет прокрутки - это не остановит прокрутку тела. Это и вообще не работает в Safari :)
waterplea

1
Я overflow-y: scrollсогласился с комбинацией этого с (из вашей демонстрации Codepen). Вопрос, поднятый @pokrishka, действителен, но в моем случае это не проблема. В любом случае, мне интересно, будут ли реализации браузеров освещать эту деталь в будущем. Я обнаружил, что в Firefox изменил размер браузера, чтобы модал не умещался на экране, а затем снова изменил его размер, чтобы это свойство работало (т. Е. Содержалась прокрутка) даже после изменения размера до полного размера - до тех пор, пока страница не будет перезагружена. , по крайней мере.
23

33

Большинство решений имеют проблему, заключающуюся в том, что они не сохраняют позицию прокрутки, поэтому я посмотрел, как это делает 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>


Это, кажется, самое элегантное решение для меня. Я знаю, что Google использует одни и те же приемы для прокрутки страницы и частого перемещения элементов.
forallepsilon

Это единственное решение, которое работает на 100% правильно для меня, я бы хотел найти этот ответ раньше.
user0103

31

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

html, body {
    overflow: hidden;
}

Для iOS нужно также установитьwidth: 100%; height: 100%;
Gavin

2
Это не решает проблему, также наблюдаемую во многих других решениях этого вопроса - если окно прокручивается где-либо, кроме верхней части страницы, когда модальное окно открыто, это приведет к прокрутке до верхней части страницы. Я нашел решение Филиппа Миттерера здесь лучшим вариантом, который охватывает большинство случаев.
CLL

1
Я никогда не сталкивался с проблемой, о которой вы говорите (ни в Интернете, ни в мобильном телефоне). Не могли бы вы поделиться полным кодом (с DOM), который не работает в скрипте или jsbin? В любом случае, я опасаюсь использовать обходные пути Javascript для решения этой проблемы.
Франциско Ходж

7

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

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

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


1
Лучшее решение, истинное мышление «из коробки», просто не так хорошо сформулировано.
Марк

Если тело не является прокручиваемым элементом, верхние панели не будут перемещаться вверх на мобильном устройстве при прокрутке страницы вниз.
Сеф Рид

7

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

  • Сверхтвердые «взмахи» пальцем все равно будут прокручиваться <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; */
}

4

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

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

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


Юп, это помогло. Просто нужно было добавить -webkit-overflow-scrolling:touch;для каждого прокручиваемого контейнера перехват сенсорных событий.
Мати

Я все еще могу прокручивать на iPhone
Симона Зандара

@SeniorSimoneZandara Я не. это прекрасно работает, чтобы предотвратить фоновую прокрутку
godblessstrawberry

3

Поведение, которое вы хотите предотвратить, называется цепочкой прокрутки. Чтобы отключить его, установите

overscroll-behavior: contain;

на вашем оверлее в CSS.


3

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

Некоторые ответы хорошие, но ни один из них не решил мою проблему. Я нашел следующее сообщение в блоге Кристоффера Петтерссона. Представленное там решение помогло решить проблему с 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">

2

Вы можете легко сделать это с некоторыми "новыми" CSS и JQuery.

Первоначально: body {... overflow:auto;} с JQuery вы можете динамически переключаться между «наложением» и «телом». Когда на «тело», используйте

body {
   position: static;
   overflow: auto;
}

Когда на наложении использовать

body {
   position: sticky;
   overflow: hidden;
}

JQuery для коммутатора ('body' -> 'overlay'):

$("body").css({"position": "sticky", "overflow": "hidden"});

JQuery для коммутатора ('overlay' -> 'body'):

$("body").css({"position": "static", "overflow": "auto"});

1

Я хотел бы добавить к предыдущим ответам, потому что я пытался сделать это, и некоторая раскладка сломалась, как только я переключил тело в положение: исправлено. Чтобы избежать этого, мне также пришлось установить рост тела на 100%:

function onMouseOverOverlay(over){
    document.getElementsByTagName("body")[0].style.overflowY = (over?"hidden":"scroll");
    document.getElementsByTagName("html")[0].style.position = (over?"fixed":"static");
    document.getElementsByTagName("html")[0].style.height = (over?"100%":"auto");
}

1

Используйте следующий HTML:

<body>
  <div class="page">Page content here</div>
  <div class="overlay"></div>
</body>

Затем JavaScript для перехвата и остановки прокрутки:

$(".page").on("touchmove", function(event) {
  event.preventDefault()
});

Затем, чтобы вернуть вещи в нормальное русло:

$(".page").off("touchmove");


1

Если целью является отключение на мобильных / сенсорных устройствах, то самый простой способ сделать это - использовать touch-action: none;.

Пример:

const app = document.getElementById('app');
const overlay = document.getElementById('overlay');

let body = '';

for (let index = 0; index < 500; index++) {
  body += index + '<br />';
}

app.innerHTML = body;
app.scrollTop = 200;

overlay.innerHTML = body;
* {
  margin: 0;
  padding: 0;
}

html,
body {
  height: 100%;
}

#app {
  background: #f00;
  position: absolute;
  height: 100%;
  width: 100%;
  overflow-y: scroll;
  line-height: 20px;
}

#overlay {
  background: rgba(0,0,0,.5);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100%;
  padding: 0 0 0 100px;
  overflow: scroll;
}
<div id='app'></div>
<div id='overlay'></div>

(Пример не работает в контексте переполнения стека. Вам нужно будет воссоздать его на отдельной странице.)

Если вы хотите отключить прокрутку #appконтейнера, просто добавьте touch-action: none;.


1
Если сенсорное действие действительно работает на iOS, то это так. И в этом случае не нужно было бы оборачивать все ваше «приложение» в отдельный div.
Powerbuoy

0

попробуй это

var mywindow = $('body'), navbarCollap = $('.navbar-collapse');    
navbarCollap.on('show.bs.collapse', function(x) {
                mywindow.css({visibility: 'hidden'});
                $('body').attr("scroll","no").attr("style", "overflow: hidden");
            });
            navbarCollap.on('hide.bs.collapse', function(x) {
                mywindow.css({visibility: 'visible'});
                $('body').attr("scroll","yes").attr("style", "");
            });

0

Если вы хотите остановить прокрутку body / html, добавьте следующее

CSS

    html, body {
        height: 100%;
    }

    .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;
    }

HTML

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

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

В принципе, вы могли бы сделать это без JS.

Основная идея заключается в добавлении html / body с высотой: 100% и переполнением: авто. и внутри вашего оверлея вы можете включить или отключить прокрутку в зависимости от ваших требований.

Надеюсь это поможет!



-2

Используйте код ниже для отключения и включения полосы прокрутки.

Scroll = (
    function(){
          var x,y;
         function hndlr(){
            window.scrollTo(x,y);
            //return;
          }  
          return {

               disable : function(x1,y1){
                    x = x1;
                    y = y1;
                   if(window.addEventListener){
                       window.addEventListener("scroll",hndlr);
                   } 
                   else{
                        window.attachEvent("onscroll", hndlr);
                   }     

               },
               enable: function(){
                      if(window.removeEventListener){
                         window.removeEventListener("scroll",hndlr);
                      }
                      else{
                        window.detachEvent("onscroll", hndlr);
                      }
               } 

          }
    })();
 //for disabled scroll bar.
Scroll.disable(0,document.body.scrollTop);
//for enabled scroll bar.
Scroll.enable();
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.