如何从可滚动容器中仅选择可见元素?
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 元素?