DOM滚动(touchmove)事件,如何找到哪个元素在滚动

4

以下是稍微有些牵强的代码:

 function h (evt) {
   if(evt.target.nodeName !=== 'POPUP') {
       evt.preventDefault()
   }
 }
 window.addEventListener('DOMMouseScroll', h, false)
 window.addEventListener('wheel', h, false)

这里尝试的目标是在弹出窗口打开时,阻止页面上除弹出窗口内部滚动之外的所有其他滚动。 但这不起作用,因为evt.target包含当前鼠标指向的元素,而不是实际滚动的那个元素。所以要么整个页面会滚动,要么只有弹出窗口内容会滚动,但无论哪种情况,evt.target都将指向弹出窗口内部的某个按钮。

但思路是要获取实际滚动的内容,是页面主体还是弹出窗口。

https://jsfiddle.net/zr9hjL4s/1/


展示一个工作的jsfiddle或codepen示例,以便快速解决问题。 - Mr Khan
2
e.stopPropagation()应该可以解决问题,而不需要知道哪个元素被滚动。 - Teemu
@Teemu,但是为了确定是否停止传播或阻止默认行为,你首先需要知道是否应该这样做,这意味着你需要知道哪个元素正在滚动。 - run_the_race
5个回答

1
似乎这里的诀窍是与js动态代码一起工作,以阻止滚动的stopPropagation和preventDefault行为,再加上隐藏溢出功能的css代码,从而使浏览器无法在元素上滚动。因此,首先要考虑的是,一旦模态框打开,您需要禁用整个页面(body)上的滚动(在实际情况下,不确定您的页面是如何构建的,但在这种情况下,我们通过添加modal-open状态并在关闭模态框时删除该类来打开经典模态框)。使用js函数openModal打开模态框。在提供的示例中,我在整个js代码执行完毕后运行此函数。因此,模态框将立即弹出。还请检查模态框内容中是否使用了css规则overflow-y,以使“内部”可滚动。
根据您的需求进行调整。您可以根据要求修改代码,但这段代码在过去多次帮助了我。:D 希望对您有所帮助。

const modalContainer = document.getElementById('modal-container');
const modalContent = document.getElementById('modal-content');

function openModal() {
  modalContainer.style.display = 'block';
  document.body.classList.add('modal-open');
  document.body.addEventListener('scroll', disableScroll);
}

function closeModal() {
  modalContainer.style.display = 'none';
  document.body.classList.remove('modal-open');
  document.body.removeEventListener('scroll', disableScroll);
}

modalContainer.addEventListener('click', (event) => {
  if (event.target === modalContainer) {
    closeModal();
  }
});

// Optional: Close the modal when the Escape key is pressed
document.addEventListener('keydown', (event) => {
  if (event.key === 'Escape') {
    closeModal();
  }
});

function disableScroll(event) {
  event.preventDefault();
  event.stopPropagation();
}

openModal()
.modal-container {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
  overflow: hidden;
}

.modal-content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: 80%;
  max-height: 80%;
  padding: 20px;
  background-color: white;
  border-radius: 10px;
  overflow-y: auto; 
}

body.modal-open {
  overflow: hidden; 
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Modal Window</title>
</head>
<body>
  <!-- Your page content goes here -->
  <div>
  What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.<br/>
What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.
   
   <br/>
   What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.<br/>
What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.

  </div>
  <!-- Modal Container -->
  <div id="modal-container" class="modal-container">
    <div id="modal-content" class="modal-content">
      <!-- Your modal content goes here -->
      <h2>Modal Window</h2>
      <p>Scroll me!!!</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.<br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.</p><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.<br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.</p><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.<br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.</p><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.<br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod purus ut facilisis elementum.</p>
      <!-- End of modal content -->
    </div>
  </div>

  <!-- End of page content -->
  <div>
  What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.<br/>
What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.
   
   <br/>
   What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.<br/>
What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.

  <br/>
   What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.<br/>
What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.

 <br/>
   What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.<br/>
What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.

 <br/>
   What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.<br/>
What it tries to achieve here is to prevent all other scrolling on the page, except inside popup when popup is opened. But this doesn't work because evt.target contains element currently pointed by mouse rather than one that actually scrolled. So either the whole page would be scrolled or just popup conents, but in both cases evt.target will point to some button inside popup.

But the idea is to get what got actually scrolled now, page body or popup.

  </div>
</body>
</html>

https://jsfiddle.net/web20opensource/14zp6btL/1/


1
在这里使用stop propagation是一种不好的做法,应该避免使用。例如,某些框架可能希望使用传播来跟踪正在发生的事情。 - Thomas Zimmermann
1
谢谢,不过这只是禁用了页面的滚动,但是模态框和页面之间可能存在可滚动的父级元素,它们也需要被禁用滚动。 - run_the_race
谢谢大家的评论。下次遇到这种情况,我会记住并牢记在心。 - mario ruiz

1

为什么不使用父级元素的溢出属性来防止滚动?

看看这种方法:

// Store previous overflow styles

const previousParentNodesOverflowStylesMap = new Map();

// Show/hide popup click handlers

document.getElementById("showPopup").addEventListener("click", () => {
  document.getElementById("popup").style.display = "block";
  disablePopupParentNodesOverflow();
});

document.getElementById("hidePopup").addEventListener("click", () => {
  document.getElementById("popup").style.display = "none";
  restorePopupParentNodesOverflow();
});

// Parents overflow CSS property management

function disablePopupParentNodesOverflow() {
  let parentNode = document.getElementById("popup");
  while ((parentNode = parentNode.parentNode)) {
    if (parentNode.style) {
      const {overflowX, overflowY, overflow} = parentNode.style;
      previousParentNodesOverflowStylesMap.set(parentNode, {
        overflowX,
        overflowY,
        overflow
      });
      parentNode.style.overflow = "hidden";
    }
  }
} 

function restorePopupParentNodesOverflow() {
  let parentNode = document.getElementById("popup");
  while ((parentNode = parentNode.parentNode)) {
    const previousOverflowStyle = previousParentNodesOverflowStylesMap.get(parentNode);
    if (previousOverflowStyle !== undefined) {
      Object.assign(parentNode.style, previousOverflowStyle); 
    }
  }
  previousParentNodesOverflowStylesMap.clear();
}
.popup {
  display: none;
  overflow-y: auto;
  background: lightgreen;
  position: absolute;
  left: 20px;
  top: 40px;
  width: 100px;
  height: 100px;
}
<!-- Creating show/hide popup system -->

<button id="showPopup">
  Show popup
</button>

<button id="hidePopup">
  Hide popup
</button>

<!-- Popup -->

<div class="popup" id="popup">
  <P>lorem ipsum dolor</P> 
  <P>lorem ipsum dolor</P> 
  <P>lorem ipsum dolor</P> 
  <P>lorem ipsum dolor</P> 
  <P>lorem ipsum dolor</P> 
  <P>lorem ipsum dolor</P> 
  <P>lorem ipsum dolor</P> 
</div>

<!-- Rest of page -->

<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 
<P>lorem ipsum dolor</P> 


它的好处是能够向用户展示他可以或者不能滚动的地方。 - Thomas Zimmermann
谢谢,但是如果可滚动的容器不是弹出窗口的父级元素,则无法起作用。例如,将“<!--页面剩余部分-->”中的<p>标签放在一个固定高度且具有overflow-y:auto属性的容器中,这样在弹出窗口显示时它仍然可以滚动。不过,在恢复可滚动状态方面做得很好。 - run_the_race

0
问题在于evt.target将引用当前鼠标指针下的元素,而这可能不是正在滚动的元素。
一个可能的解决方案是使用evt.target来检查滚动容器。为了做到这一点,我们可以使用while循环来遍历目标元素的父级,直到找到允许滚动的那个元素。以下是一个修订后的函数,实现了这个功能:

function h(evt) {
    let node = evt.target;
    while (node !== null) {
        if (node === document.querySelector('.popup') && node.scrollHeight > node.clientHeight) {
            return; // Scrolling is within the popup, so we allow it
        }
        node = node.parentNode;
    }
    evt.preventDefault(); // The scrolling is not within the popup, so we prevent it
}

在这个函数中,使用node.scrollHeight > node.clientHeight的检查来确定元素是否可滚动。如果scrollHeight(包括由于滚动而隐藏的部分)大于clientHeight(可见高度),则元素是可滚动的。
此外,您需要将.popup替换为实际的弹出元素选择器。
您可以像以前一样添加事件监听器:

window.addEventListener('DOMMouseScroll', h, false)
window.addEventListener('wheel', h, false)

请告诉我这个解决方案是否适用于您的情况!

谢谢回复,但是即使根据您的检查,一个项目是可滚动的,一旦到达底部,滚动条会"溢出"并开始应用于其父级元素。 - run_the_race

0
为了解决这个问题,你可以尝试一种不同的方法。你可以检查目标元素的滚动容器是主体还是弹出窗口。
示例:
function h (evt) {
   var currentElement = evt.target;
   while(currentElement) {
       if(currentElement === document.body) {
           evt.preventDefault();
           break;
       }
       if(currentElement.nodeName === 'POPUP') {
           break;
       }
       currentElement = currentElement.parentNode;
   }
}

window.addEventListener('DOMMouseScroll', h, false);
window.addEventListener('wheel', h, false);

编辑 - 另一个版本:

据我所了解,可能存在多个嵌套的可滚动元素。为了处理这种情况,我会使用Element.scrollWidthElement.scrollHeight属性。如果一个元素的scrollWidth/scrollHeight大于它的clientWidth/clientHeight,那么它就是一个可滚动的元素。

我也尝试了你的jsfiddle,看起来运行正常 - 也许我漏掉了什么 - 更详细的解释会很有帮助。

function h (evt) {
   var currentElement = evt.target;
   while(currentElement && currentElement !== document.body) {
       if(currentElement.nodeName === 'POPUP' || isScrollable(currentElement)) {
           break;
       }
       currentElement = currentElement.parentNode;
   }

   if(currentElement === document.body || !currentElement) {
       evt.preventDefault();
   }
}

function isScrollable(el) {
    return (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth);
}

window.addEventListener('DOMMouseScroll', h, false);
window.addEventListener('wheel', h, false);

在当前元素和页面主体之间可能存在一个可滚动的父级元素。 - run_the_race
@run_the_race 请看一下 - jagmitg
一个元素即使其scrollHeight > clientHeight,也可能是*不可滚动的,例如overflow: clip就是这样的情况。此外,一个具有position: fixed属性的元素即使其父元素可滚动,也不会导致父元素滚动。 - Kaiido
DOMMouseScroll事件会冒泡到层次结构中,所以while循环在功能上并没有提供任何新的东西,但是通过将一个算法强制转换为²性能,它产生了操作成本。如果我们移除对isScrollable()的调用(这种判别方式不好且不必要,使用overscroll-behavior: none;更容易为非弹出窗口设置样式),剩下的代码可以简化为以下箭头函数等效形式:h = (evt) => evt.target===document.body ? evt.preventDefault() : undefined。然而,这并不能完全解决问题,还需要.stopPropagation()和一个监听器来应用/移除CSS。 - CJK

0
为了实现在打开弹出窗口时阻止整个页面滚动的期望行为,您可以修改事件监听器和preventDefault函数。不仅仅依赖于evt.target属性,您可以检查触发滚动事件的元素是否位于弹出窗口内部。以下是更新后的代码:
在h函数中,检查目标元素或其任何父级是否具有类名为"scroll"(表示弹出窗口)。如果是,则允许滚动;否则,阻止默认的滚动行为:
function h(evt) {
  const targetElement = evt.target;
  if (!targetElement.closest('.scroll')) {
    evt.preventDefault();
  }
}

打开弹出窗口时,请确保调用disableScrollExcept函数并传递弹出窗口元素。
function dis() {
  let e = document.getElementById('scroll');
  disableScrollExcept(e);
}

通过这些更改,当弹出窗口打开并尝试在其中滚动时,滚动将被允许。当您尝试在弹出窗口外滚动时,将阻止默认的滚动行为。这样,您可以实现所需的滚动效果。

谢谢,我觉得你忘记发布disableScrollExcept函数的代码了吗? - run_the_race

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接