CSS 防止身体滚动但允许覆盖滚动

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9280258/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 02:58:22  来源:igfitidea点击:

Prevent body scrolling but allow overlay scrolling

cssoverlaylightbox

提问by PotatoFro

I've been searching for a "lightbox" type solution that allows this but haven't found one yet (please, suggest if you know of any).

我一直在寻找允许这样做的“灯箱”类型的解决方案,但还没有找到(如果您知道,请提出建议)。

The behavior I'm trying to recreate is just like what you'd see at Pinterestwhen clicking on an image. The overlay is scrollable (as in the whole overlay moves up like a page on top of a page) but the body behindthe overlay is fixed.

我试图重现的行为就像您在Pinterest 上点击图像时看到的一样。覆盖层是可滚动的(因为整个覆盖层像页面顶部的页面一样向上移动),但覆盖层后面的主体是固定的。

I attempted to create this with just CSS (i.e. a divoverlay on top of the whole page and body with overflow: hidden), but it doesn't prevent divfrom being scrollable.

我试图只用 CSS(div在整个页面和正文顶部的覆盖层overflow: hidden)创建它,但它并不能阻止div可滚动。

How to keep the body/page from scrolling but keep scrolling inside the fullscreen container?

如何防止正文/页面滚动但在全屏容器内继续滚动?

回答by Fabrizio Calderan

Theory

理论

Looking at current implementation of the pinterest site (it might change in the future), when you open the overlay a noscrollclass is applied to the bodyelement and overflow: hiddenis set, thus bodyis no longer scrollable.

查看 pinterest 站点的当前实现(将来可能会更改),当您打开覆盖层时,一个noscroll类将应用于body元素并overflow: hidden设置,因此body不再可滚动。

The overlay (created on-the-fly or already inside the page and made visible via display: block, it makes no difference) has position : fixedand overflow-y: scroll, with top, left, rightand bottomproperties set to 0: this style makes the overlay fill the whole viewport.

叠加(上即时或已经在页面内,并通过可见创建display: block,它使没有区别)有position : fixedoverflow-y: scroll,与topleftrightbottom属性设置为0:这种风格使得覆盖填充整个视口。

The divinside the overlay is instead just in position: staticthen the vertical scrollbar you see is related to that element. As a result the content is scrollable but overlay remains fixed.

div覆盖层的内部正好在position: static然后您看到的垂直滚动条与该元素相关。因此,内容是可滚动的,但叠加层保持固定。

When you close the zoom you hide the overlay (via display: none) and then you could also entirely remove it via javascript (or just the content inside, it's up to you how to inject it).

当您关闭缩放时,您会隐藏覆盖层(通过display: none),然后您也可以通过 javascript 完全删除它(或者只是里面的内容,这取决于您如何注入它)。

As a final step you have to also remove the noscrollclass to the body(so the overflow property returns to its initial value)

作为最后一步,您还必须将noscroll类删除到body(因此溢出属性返回其初始值)



Code

代码

Codepen Example

代码笔示例

(it works by changing the aria-hiddenattribute of the overlay in order to show and hide it and to increase its accessibility).

(它通过更改aria-hidden覆盖层的属性来显示和隐藏它并增加其可访问性来工作)。

Markup
(open button)

标记
(打开按钮)

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

(overlay and close 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

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)

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);

});


Finally, here's another example in which the overlay opens with a fade-in effect by a CSS transitionapplied to the opacityproperty. Also a padding-rightis applied to avoid a reflow on the underlying text when the scrollbar disappears.

最后,这是另一个示例,其中叠加层通过transition应用于opacity属性的 CSS 以淡入效果打开。padding-right当滚动条消失时,还应用 a 来避免底层文本的回流。

Codepen Example (fade)

Codepen 示例(淡入淡出)

CSS

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

回答by am80l

If you want to prevent overscrolling on ios, you can add position fixed to your .noscroll class

如果你想防止在 ios 上过度滚动,你可以添加固定到你的 .noscroll 类的位置

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

回答by Lucia

Don't use overflow: hidden;on body. It automatically scrolls everything to the top. There's no need for JavaScript either. Make use of overflow: auto;. This solution even works with mobile Safari:

不要overflow: hidden;body. 它会自动将所有内容滚动到顶部。也不需要 JavaScript。利用overflow: auto;. 此解决方案甚至适用于移动 Safari:

HTML Structure

HTML 结构

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

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

Styling

造型

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

See the demo hereand source code here.

观看演示在这里和源代码在这里

Update:

更新:

For people who want keyboard space bar, page up/down to work: you need to focus on the overlay, e.g., clicking on it, or manually JS focusing on it before this part of the divwill respond to keyboard. Same with when the overlay is "switched off", since it's just moving the overlay to the side. Otherwise to browser, these are just two normal divs and it wouldn't know why it should focus on any one of them.

对于想要键盘空格键,上下翻页的人:你需要专注于覆盖,例如点击它,或者手动JS专注于它之前这部分会div响应键盘。与“关闭”叠加层时相同,因为它只是将叠加层移到一边。否则对于浏览器来说,这些只是两个普通的divs,它不知道为什么它应该关注其中任何一个。

回答by Igor Alemasow

overscroll-behaviorcss property allows to override the browser's default overflow scroll behavior when reaching the top/bottom of content.

overscroll-behaviorcss 属性允许在到达内容的顶部/底部时覆盖浏览器的默认溢出滚动行为。

Just add the following styles to overlay:

只需添加以下样式即可叠加:

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

Codepen demo

代码笔演示

Currently works in Chrome, Firefox and IE(caniuse)

目前适用于 Chrome、Firefox 和 IE(caniuse

For more details check google developers article.

有关更多详细信息,请查看谷歌开发者文章

回答by Philipp Mitterer

Most solutions have the problem that they do not retain the scroll position, so I took a look at how Facebook does it. In addition to setting the underlaying content to position: fixedthey also set the top dynamically to retain the scroll position:

大多数解决方案都存在不保留滚动位置的问题,因此我查看了 Facebook 是如何做到的。除了将底层内容设置为position: fixed他们还动态设置顶部以保留滚动位置:

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

Then, when you remove the overlay again, you need to reset the scroll position:

然后,当您再次删除覆盖时,您需要重置滚动位置:

window.scrollTo(0, scrollPosition);

I created a little example to demonstrate this solution

我创建了一个小例子来演示这个解决方案

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>

回答by Francisco Hodge

It is worth noting that sometimes adding "overflow:hidden" to the body tag doesn't do the job. In those cases, you'll have to add the property to the html tag as well.

值得注意的是,有时将 "overflow:hidden" 添加到 body 标签并不能完成这项工作。在这些情况下,您还必须将该属性添加到 html 标记中。

html, body {
    overflow: hidden;
}

回答by NGLN

Generally speaking, if you want a parent (the body in this case) to prevent it from scrolling when a child (the overlay in this case) scrolls, then make the child a sibling of the parent to prevent the scroll event from bubbling up to the parent. In case of the parent being the body, this requires an additional wrapping element:

一般来说,如果您希望父级(本例中的主体)在子级(本例中的叠加层)滚动时阻止其滚动,则使子级成为父级的兄弟,以防止滚动事件冒泡到父母。如果父对象是主体,则需要一个额外的包装元素:

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

See Scroll particular DIV contents with browser's main scrollbarto see its working.

请参阅使用浏览器的主滚动条滚动特定 DIV 内容以查看其工作情况。

回答by Weston

The chosen answer is correct, but has some limitations:

选择的答案是正确的,但有一些限制:

  • Super hard "flings" with your finger will still scroll <body>in the background
  • Opening the virtual keyboard by tapping an <input>in the modal will direct all future scrolls to <body>
  • 用手指超硬“甩”仍会<body>在后台滚动
  • 通过<input>在模态中点击打开虚拟键盘将引导所有未来的滚动到<body>

I don't have a fix for the first issue, but wanted to shed some light on the second. Confusingly, Bootstrap used to have the keyboard issue documented, but they claimed it was fixed, citing http://output.jsbin.com/cacido/quietas an example of the fix.

我没有解决第一个问题,但想阐明第二个问题。令人困惑的是,Bootstrap 曾经记录了键盘问题,但他们声称它已修复,并引用http://output.jsbin.com/cacido/quiet作为修复示例。

Indeed, that example works fine on iOS with my tests. However, upgrading it to the latest Bootstrap (v4) breaks it.

事实上,这个例子在我的测试中在 iOS 上运行良好。但是,将其升级到最新的 Bootstrap (v4) 会破坏它。

In an attempt to figure out what the difference between them was, I reduced a test case to no longer depend on Bootstrap, http://codepen.io/WestonThayer/pen/bgZxBG.

为了弄清楚它们之间的区别是什么,我减少了一个测试用例,不再依赖于 Bootstrap,http://codepen.io/WestonThayer/pen/bgZxBG

The deciding factors are bizarre. Avoiding the keyboard issue seems to require that background-coloris notset on the root <div>containing the modal andthe modal's content must be nested in another <div>, which can have background-colorset.

决定因素很奇怪。避免键盘问题似乎需要background-color不在<div>包含模态的根上设置,并且模态的内容必须嵌套在另一个<div>可以background-color设置的 .

To test it, uncomment the below line in the Codepen example:

要测试它,请取消注释 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; */
}

回答by YarGnawh

For touch devices, try adding a 1px wide, 101vhmin-height transparent div in the wrapper of the overlay. Then add -webkit-overflow-scrolling:touch; overflow-y: auto;to the wrapper. This tricks mobile safari into thinking the overlay is scrollable, thus intercepting the touch event from the body.

对于触摸设备,尝试101vh在覆盖层的包装器中添加一个 1px 宽、最小高度的透明 div。然后添加-webkit-overflow-scrolling:touch; overflow-y: auto;到包装中。这会诱使移动 safari 认为覆盖层是可滚动的,从而拦截来自身体的触摸事件。

Here's a sample page. Open on mobile safari: http://www.originalfunction.com/overlay.html

这是一个示例页面。在手机 safari 上打开:http: //www.originalfunction.com/overlay.html

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

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

回答by TheFullResolution

I found this question trying to solve issue I had with my page on Ipad and Iphone - body was scrolling when I was displaying fixed div as popup with image.

我发现这个问题试图解决我在 Ipad 和 Iphone 上的页面遇到的问题 - 当我将固定 div 显示为带有图像的弹出窗口时,正文正在滚动。

Some answers are good, however none of them solved my issue. I found following blog post by Christoffer Pettersson. Solution presented there helped issue I had with iOS devices and it helped my scrolling background problem.

有些答案很好,但是没有一个解决了我的问题。我找到了 Christoffer Pettersson 的以下博客文章。那里提出的解决方案帮助了我在 iOS 设备上遇到的问题,它帮助了我的滚动背景问题。

Six things I learnt about iOS Safari's rubber band scrolling

我从 iOS Safari 的橡皮筋滚动中学到的六件事

As it was suggested I include major points of the blog post in case link gets outdated.

正如有人建议的那样,我将博客文章的要点包括在内,以防链接过时。

"In order to disable that the user can scroll the background page while the "menu is open", it is possible to control what elements should be allowed to be scrolled or not, by applying some JavaScript and a CSS class.

“为了禁用用户可以在“菜单打开”时滚动背景页面,可以通过应用一些 JavaScript 和 CSS 类来控制应该允许或不允许滚动哪些元素。

Based on this Stackoverflowanswer you can control that elements with the disable-scrolling should not perform their default scroll action when the touchmove event is triggered."

基于此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();
    }
};

And then put the disable-scrolling class on the page div:

然后将禁用滚动类放在页面 div 上:

<div class="page disable-scrolling">