查找第一个可滚动的父项

Find first scrollable parent

本文关键字:滚动 第一个 查找      更新时间:2023-09-26

我遇到这种情况,我需要将元素滚动到视口中。问题是我不知道哪个元素是可滚动的。例如,在纵向中,身体是可滚动的,而在横向中是另一个元素(并且还有更多情况会更改可滚动元素)

现在的问题是,给定一个需要滚动到视口中的元素,找到其第一个可滚动父元素的最佳方法是什么?

我在这里设置了一个演示。使用按钮,您可以在两种不同的情况之间切换

<div class="outer">
    <div class="inner">
        <div class="content"> 
            ...
            <span>Scroll me into view</span>
        </div>
    </div>
</div>

正文可滚动或.outer

有什么建议吗?

只需检查滚动条是否可见,如果不查看父级。

function getScrollParent(node) {
  if (node == null) {
    return null;
  }
  if (node.scrollHeight > node.clientHeight) {
    return node;
  } else {
    return getScrollParent(node.parentNode);
  }
}
这是

cweston所说的jQuery UI scrollParent方法的纯JS端口。我选择了这个而不是接受的答案的解决方案,如果还没有内容溢出,则找不到滚动父级。

与我的端口的一个区别是,如果没有找到具有 CSS overflow 属性正确值的父级,我将返回 <body> 元素。JQuery UI,而是返回document对象。这很奇怪,因为像.scrollTop这样的值可以从<body>中检索,但不能从document中检索。

function getScrollParent(element, includeHidden) {
    var style = getComputedStyle(element);
    var excludeStaticParent = style.position === "absolute";
    var overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
    if (style.position === "fixed") return document.body;
    for (var parent = element; (parent = parent.parentElement);) {
        style = getComputedStyle(parent);
        if (excludeStaticParent && style.position === "static") {
            continue;
        }
        if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
    }
    return document.body;
}
大多数

投票的答案并非在所有情况下都有效scrollHeight > clientHeight即使没有滚动条也可以true

我发现这个要点解决方案 https://github.com/olahol/scrollparent.js/blob/master/scrollparent.js#L13

^ 总功劳归于编写代码 https://github.com/olahol。

将其重构为es6

export const getScrollParent = (node) => {
  const regex = /(auto|scroll)/;
  const parents = (_node, ps) => {
    if (_node.parentNode === null) { return ps; }
    return parents(_node.parentNode, ps.concat([_node]));
  };
  const style = (_node, prop) => getComputedStyle(_node, null).getPropertyValue(prop);
  const overflow = _node => style(_node, 'overflow') + style(_node, 'overflow-y') + style(_node, 'overflow-x');
  const scroll = _node => regex.test(overflow(_node));
  /* eslint-disable consistent-return */
  const scrollParent = (_node) => {
    if (!(_node instanceof HTMLElement || _node instanceof SVGElement)) {
      return;
    }
    const ps = parents(_node.parentNode, []);
    for (let i = 0; i < ps.length; i += 1) {
      if (scroll(ps[i])) {
        return ps[i];
      }
    }
    return document.scrollingElement || document.documentElement;
  };
  return scrollParent(node);
  /* eslint-enable consistent-return */
};

您可以像这样使用它:

const $yourElement = document.querySelector('.your-class-or-selector');
getScrollParent($yourElement);

如果你使用的是jQuery UI,你可以使用scrollParent方法。查看 API 或源代码。

从 API :

.scrollParent():获取可滚动的最接近的祖先元素

此方法不接受任何参数。此方法 查找允许滚动的最近祖先。换句话说, .scrollParent()方法查找当前选定的元素 元素将在内部滚动。

注意:此方法仅适用于包含一个元素的 jQuery 对象。

如果您没有使用 jQuery

UI,而是使用 jQuery,那么还有其他提供类似功能的独立库,例如:

jquery-scrollparent

使用谷歌浏览器开发工具,当您部分向下滚动页面时,检查页面,选择您认为可能是正在滚动的 DOM 节点。然后拉起控制台(从开发工具的"元素"选项卡中点击 ESC)并键入 $0.scrollTop 。这将打印出该元素的当前滚动位置。如果它不是 0,那么您将知道这是正在滚动的元素。

我想你想要这个。

$('button').click(function() {
  $("body").addClass("body");
  $('.outer').toggleClass('scroller');
  check($(".content"));
});
function check(el) {
  var overflowY = el.css("overflow-y");  
  if (overflowY == "scroll") {
    alert(el.attr("class") + " has");
  } else {
    if(el.parent().length > 0)
   	  check(el.parent());
    else 
      return false;
  }
}
body {
  height: 450px;
  overflow-y: scroll;
}
div.inner {
  width: 200px;
  height: 400px;
  border: 1px solid #000;
}
div.outer {
  width: 200px;
  height: 200px;
}
div.outer.scroller {
  overflow-y: scroll;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>
  toggle
</button>
<div class="outer">
  <div class="inner">
    <div class="content">
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
      in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." adipiscing elit, sed do eiusmod tempor incididunt ut
      labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
      sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
    </div>
  </div>
</div>

进一步基于@Web_Designer的答案,

如果您正在传递该元素的jQuery object并收到以下错误,

Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'

然后尝试只传递 btw 驻留在数组键0Dom Node element如果元素是单个元素。例如。

getScrollParent(jQuery("#" + formid)[0])

这就是我目前所拥有的似乎适用于 Web 组件自定义元素 shadowRoots; 我们可以进一步扩展它,以便在滚动开始卸载此处理并跟踪特定节点的滚动事件后添加滚动事件处理程序

function scroller(node){ return node.scrollTop }
function handler(event){
        const path = event.composedPath();
        const scrollNode = path.find(scroller);
        const scrollNodes = path.filter(scroller);
        console.warn('scroll!',{scrollNode, scrollNodes});
}
window.addEventListener('mousewheel', handler, {capture: true});
window.addEventListener('keydown', handler, {capture: true});

@ncubica答案的更有效版本。

在这个版本中,我们逐个检查每个父项,并停在第一个可滚动的父项上。此外,每个父级只调用一次getComputedStyle()

const isScrollable = (node: Element) => {
  if (!(node instanceof HTMLElement || node instanceof SVGElement)) {
    return false
  }
  const style = getComputedStyle(node)
  return ['overflow', 'overflow-x', 'overflow-y'].some((propertyName) => {
    const value = style.getPropertyValue(propertyName)
    return value === 'auto' || value === 'scroll'
  })
}
export const getScrollParent = (node: Element): Element => {
  let currentParent = node.parentElement
  while (currentParent) {
    if (isScrollable(currentParent)) {
      return currentParent
    }
    currentParent = currentParent.parentElement
  }
  return document.scrollingElement || document.documentElement
}

这是找到你想要的东西的方法。

document.addEventListener('scroll', (e) => {
    console.log(e.target.scrollingElement.scrollTop);
})