【问题标题】: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>      

【问题讨论】:

    标签: javascript angular typescript


    【解决方案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(意味着只进行了所需的更改)。

      【讨论】:

        猜你喜欢
        • 2017-06-27
        • 1970-01-01
        • 2015-09-15
        • 1970-01-01
        • 2020-11-03
        • 2014-07-10
        • 2020-08-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多