防止正文滚动,但允许叠加滚动



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

我试图重新创建的行为就像你在Pinterest上看到的点击图像一样。叠加层是可滚动的(因为整个叠加层像页面顶部的页面一样向上移动(,但叠加层后面的正文是固定的。

我试图只用CSS创建它(即在整个页面和正文顶部的div覆盖overflow: hidden(,但这并不妨碍div可滚动。

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

理论

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

即时创建或已注入页面并通过display: block可见的叠加层 - 没有区别 - 具有position : fixedoverflow-y: scrolltopleftrightbottom属性设置为0:这种样式使叠加层填充整个视口(但现在我们在2022年, 所以你可以改用inset: 0(。

叠加层内的div position: static,因此垂直滚动条与该元素相关。这会导致可滚动但固定的覆盖。

当你关闭覆盖层时,你必须隐藏它(使用display: none(,你甚至可以通过javascript删除节点(或者只是里面的内容,这取决于你,但也取决于内容的性质(。

最后一步是删除应用于bodynoscroll 类(以便 overflow 属性恢复为以前的值(

<小时 />

法典

代码笔示例

(它的工作原理是更改叠加层的 aria-hidden 属性,以显示和隐藏它并增加其可访问性(。

标记
(打开按钮(

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

(叠加和关闭按钮(

<section class="overlay" aria-hidden="true" tabindex="-1">
  <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;
   inset: 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"]'),
    openingBtt;
    
[].forEach.call(overlayBtts, function(btt) {
  btt.addEventListener('click', function() { 
     
     /* Detect the button class name */
     var overlayOpen = this.className === 'open-overlay';
     
     /* storing a reference to the opening button */
     if (overlayOpen) {
        openingBtt = this;
     }
     
     /* 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;
     
      /* forcing focus for Assistive technologies but note:
    - if your modal has just a phrase and a button move the
      focus on the button
    - if your modal has a long text inside (e.g. a privacy
      policy) move the focus on the first heading inside 
      the modal
    - otherwise just focus the modal.
    When you close the overlay restore the focus on the 
    button that opened the modal.
    */
    if (overlayOpen) {
       overlay.focus();
    }
    else {
       openingBtt.focus();
       openingBtt = null;
    }
  }, false);
});
/* detect Escape key when the overlay is open */
document.body.addEventListener('keyup', (ev) => {
   if (ev.key === "Escape" && overlay.getAttribute('aria-hidden') === 'false') {
      overlay.setAttribute('aria-hidden', 'true');
      body.classList.toggle('noscroll', false);
      openingBtt.focus();
      openingBtt = null;
   }
})
<小时 />

最后,下面是另一个示例,其中覆盖层通过应用于 opacity 属性的 CSS transition以淡入效果打开。此外,还应用了padding-right以避免滚动条消失时在基础文本上重排。

代码笔示例(淡入淡出(

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

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

只需将以下样式添加到叠加层:

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

代码笔演示

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

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

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

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

大多数解决方案都存在不保留滚动位置的问题,所以我看了一下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 */
/* Suggestion to put: overflow-y: hidden; 
Disabled scrolling still makes a mess with its width. Hiding it does the trick. */
}
.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>

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

网页结构

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

在此处查看演示,在此处查看源代码。

更新:

对于想要键盘空格键的人,向上/向下翻页工作:您需要专注于覆盖层,例如,单击它,或者在div的这一部分响应键盘之前手动 JS 关注它。与"关闭"覆盖层时相同,因为它只是将叠加层移动到一侧。否则对于浏览器来说,这只是两个正常的div,它不知道为什么它应该专注于其中任何一个。

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

html, body {
    overflow: hidden;
}

您要防止的行为称为滚动链接。要禁用它,请设置

overscroll-behavior: contain;

在 CSS 中的叠加层上。

你可以用一些"新"的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"});

如果有人正在寻找 React 函数组件的解决方案,你可以把它放在模态组件中:

 useEffect(() => {
    document.body.style.overflowY = 'hidden';
    return () =>{
      document.body.style.overflowY = 'auto';
    }
  }, [])

一般来说,如果您希望父级(在本例中为正文(在子项(在本例中为覆盖层(滚动时阻止其滚动,则使子项成为父项的同级,以防止滚动事件冒泡到父级。如果父级是主体,则需要一个额外的包装元素:

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

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

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

  • 用手指超硬的"甩动"仍然会在后台滚动<body>
  • 通过点击模式中的<input>打开虚拟键盘会将所有未来的滚动定向到<body>

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

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

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

决定因素很奇怪。避免键盘问题似乎要求在包含模态的根<div>上不设置background-color,并且模态的内容必须嵌套在另一个<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; */
}

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

下面是一个示例页面。在移动野生动物园上开放:http://www.originalfunction.com/overlay.html

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

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

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

我学到的关于iOS Safari橡皮筋滚动的六件事

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

">

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

基于这个 Stackoverflow 答案,您可以控制具有禁用滚动的元素不应在触发触摸移动事件时执行其默认滚动操作。

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

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

<div class="page disable-scrolling">

正文标签的简单内联样式:

<body style="position: sticky; overflow: hidden;">

如果目的是在移动/触摸设备上禁用,那么最直接的方法就是使用 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;

我想添加到以前的答案中,因为我试图这样做,当我将正文切换到 position:fixed 时,一些布局就中断了。为了避免这种情况,我还必须将身高设置为 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");
}

使用以下 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");

就我而言,这些解决方案都没有在iPhone(iOS 11.0(上奏效。

在我的所有设备上工作的唯一有效修复程序是这个 - ios-10-safari-prevent-scrolling-behind a-fixed-overlay-and-maintenance-scroll-position

试试这个

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", "");
            });

React 功能组件的一个解决方案是使用 useEffect 钩子

下面是下面的代码示例(注意 useEffect 定义(:

import {useEffect, useRef} from "react";
export default function PopoverMenu({className, handleClose, children}) {
  const selfRef = useRef(undefined);
  useEffect(() => {
    const isPopoverOpenned = selfRef.current?.style.display !== "none";
    const focusedElement = document?.activeElement;
    const scrollPosition = {x: window.scrollX, y: window.scrollY};
    if (isPopoverOpenned) {
      preventDocBodyScrolling();
    } else {
      restoreDocBodyScrolling();
    }
    function preventDocBodyScrolling() {
      const width = document.body.clientWidth;
      const hasVerticalScrollBar = (window.innerWidth > document.documentElement.clientWidth);
      document.body.style.overflowX = "hidden";
      document.body.style.overflowY = hasVerticalScrollBar ? "scroll" : "";
      document.body.style.width = `${width}px`;
      document.body.style.position = "fixed";
    }
    function restoreDocBodyScrolling() {
      document.body.style.overflowX = "";
      document.body.style.overflowY = "";
      document.body.style.width = "";
      document.body.style.position = "";
      focusedElement?.focus();
      window.scrollTo(scrollPosition.x, scrollPosition.y);
    }

    return () => {
      restoreDocBodyScrolling(); // cleanup on unmount
    };
  }, []);
  return (
    <>
      <div
        className="backdrop"
        onClick={() => handleClose && handleClose()}
      />
      <div
        className={`pop-over-menu${className ? (` ${className}`) : ""}`}
        ref={selfRef}
      >
        <button
          className="pop-over-menu--close-button" type="button"
          onClick={() => handleClose && handleClose()}
        >
          X
        </button>
        {children}
      </div>
    </>
  );
}

最初发布在这个另一个相关的Stackoverflow问题:https://stackoverflow.com/a/69016517/14131330

CSS

.noScroll {
    overflow: hidden;
}

爪哇语

<script>
    function toggleNav() {
        document.body.classList.toggle("noScroll");
    }
</script>

按钮

<button onclick="toggleNav()">
 Toggle Nav
</button>
如果要

停止正文/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的情况下做到这一点。

主要思想是添加高度:100%和溢出:自动的html/正文。在叠加层中,您可以根据需要启用/禁用滚动。

希望这有帮助!

使用以下代码禁用和启用滚动条。

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

最新更新