如何从可滚动容器中仅选择可见元素?

Posted

技术标签:

【中文标题】如何从可滚动容器中仅选择可见元素?【英文标题】:How to select only visible elements from scrollable container? 【发布时间】:2022-01-23 20:41:15 【问题描述】:

是否可以在可滚动容器中获取可见元素列表?使用滚动条时,屏幕上可见元素的数量明显发生变化——因此,在最后两个可见元素中添加特定的类是非常困难的。

你有什么想法吗?

  <div class="scrollable-container">
    <div *ngFor="let item of items">
       item?.Name 
    </div>
  </div>      

【问题讨论】:

【参考方案1】:

侦听文档上的scroll 事件,然后确定边界矩形相对于视口的位置,您可以确定元素是否可见以及在何处可见:

只需将div#question 替换为您感兴趣的元素的定位器即可。

document.addEventListener('scroll', function(e) 
    var rect = document.querySelector('div#question').getBoundingClientRect();
    if (rect.top > window.innerHeight) 
        console.log("Element below viewport");
     else if (rect.top > 0 && rect.top < window.innerHeight && rect.bottom > window.innerHeight) 
        console.log("Element partly visible at bottom");
     else if (rect.top > 0 && rect.top < window.innerHeight && rect.bottom < window.innerHeight) 
        console.log("Element fully visible");
     else if (rect.top < 0 && rect.bottom > 0) 
        console.log("Element partly visible at top");
     else if (rect.bottom < 0) 
        console.log("Element above viewport");
    
);

【讨论】:

【参考方案2】:

这是我所知道的在本机 javascript 中执行此操作的最高效的方式。 Angular 的概念是相同的,但您需要进行一些更改,并希望进行其他更改。我将在示例之后进行介绍。

您可能希望全屏查看示例。

(请注意,如果三分之一的一部分部分可见,则可能会突出显示两个以上的项目。一旦您阅读了下面的 threshold 选项,您就可以使用它来调整此行为)。

const list = document.querySelector('.scrollable-container');

const options =  
  root: list,
  rootMargin: '-150px 0px 0px 0px',
  threshold: 0
;

function onIntersectionChange(entries) 
  entries.forEach(entry => 
    if (entry.isIntersecting) 
      entry.target.classList.add('highlighted');
    else 
      entry.target.classList.remove('highlighted');
  );


const observer = new IntersectionObserver(onIntersectionChange, options);


  const listItems = list.children;
  for (let i = 0; i < listItems.length; i++) 
    observer.observe(listItems[i]);
  
.scrollable-container 
  height: 200px;
  border: 1px solid black;
  overflow: auto;


.item 
  padding: 10px 0;  


.highlighted 
  background-color: blue;
  color: white;
<div class="scrollable-container">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
  <div class="item">Item 4</div>
  <div class="item">Item 5</div>
  <div class="item">Item 6</div>
  <div class="item">Item 7</div>
  <div class="item">Item 8</div>
  <div class="item">Item 9</div>
  <div class="item">Item 10</div>
  <div class="item">Item 11</div>
  <div class="item">Item 12</div>
  <div class="item">Item 13</div>
  <div class="item">Item 14</div>
  <div class="item">Item 15</div>
  <div class="item">Item 16</div>
  <div class="item">Item 17</div>
  <div class="item">Item 18</div>
  <div class="item">Item 19</div>
  <div class="item">Item 20</div>
</div>

我正在使用Intersection Observer API 来侦听列表中的项目与列表容器元素底部之间的交集。每次观察到的项目之一从“相交”变为“不相交”时,API 都会调用我的处理程序方法。

为了进行设置,我向观察者提供了一个名为 options 的对象,并设置了以下选项:

root:列表容器。 rootMargin:与​​root 的实际边界框的偏移量。可以将其想象为将目标root 的实际大小缩小了一些。在这里,我将大小从顶部减小了 100 像素。如果一个元素超出了列表容器中的这条假想线,它将不再被视为“相交”。 threshold:元素的多少(范围从 0 到 1.0)必须在 root 内才能被视为“相交”。虽然 0 是默认值,但为了清楚起见,我将其包括在内。这里的 0 表示如果有任何像素进入root,则该元素被视为“相交”,直到最后一个像素离开。如果将其设置为 1.0,则元素中的所有像素都必须在 root 中,然后才被视为“相交”,并且只要离开root 一个像素,就将其视为“不相交”。李>

接下来我创建了回调,每当观察到的任何元素从“相交”变为“不相交”时都会执行,反之亦然。 entries 是状态改变的元素的集合。如果元素现在与root 相交,我们添加适当的 CSS 类。如果它不再与root 相交,我们将删除该类。

最后,我创建观察者并将列表中的每个项目注册为应观察的条目。注册在一个功能块内,以保持listItems 仅在我们需要时可用。一旦注册了元素,它将超出范围。


关于 Angular,你必须做出改变:

因为您使用的是ngFor,所以在呈现模板之前您无法注册项目。这意味着您的组件将需要实现AfterViewInit 并在ngAfterViewInit() 中向观察者注册项目。

您可能还想进行一些更改:

Angular 可以访问像 @ViewChild 这样的指令,这使得从模板中选择项目比使用本机 querySelector/getElementById 方法更安全,尤其是在重新渲染模板时。 Angular 使用 TypeScript,因此您需要键入变量以便于测试、错误检查等。由于 Intersection Observer API 是原生 JavaScript,因此存在现有的 TypeScript 类型;你不需要自己写。 Angular 设置为与适当的 rxjs 可观察对象一起工作,您可能会通过使用它们来进一步提高性能,例如调用处理函数时去抖动,观察特定项目何时更改交集状态等。有几篇关于在 Angular 中使用 Intersection Observer API 的不错的博客文章涵盖了这些主题。

Here's a StackBlitz 以最省力的方式移植到 Angular(意味着只进行了所需的更改)。

【讨论】:

以上是关于如何从可滚动容器中仅选择可见元素?的主要内容,如果未能解决你的问题,请参考以下文章

Vuejs - 如何在单个表单中仅提交可见元素(使用 Vuelidate)

在 iOS 中,当用户滚动浏览 WKWebView 时,如何检测屏幕上可见的 html 元素?

如何知道元素是不是对用户可见? [复制]

如何使元素与页面宽度一样宽,包括滚动?

如何使用 Xamarin UITest 滚动到页面/容器的顶部?

如何滚动到硒中的特定元素?