What is the best method for choosing visible elements within a scrollable container?

Is there a way to retrieve the list of visible elements within a scrollable container? The number of elements that are visible on the screen changes as one scrolls, making it challenging to add a specific class to only the last two visible elements.

Any suggestions or ideas?

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

Answer №1

To determine the visibility of an element, you can listen for scroll events on the document and calculate its position relative to the viewport:

Simply modify the selector div#question with the appropriate locator for your desired element.

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");
    }
});

Answer №2

One effective method I've discovered for achieving this in pure JavaScript is by utilizing the Intersection Observer API. Although the fundamental concept remains consistent for Angular, adjustments are necessary, as well as potential enhancements. These modifications will be discussed following the provided example.

To fully grasp the example, consider viewing it in full screen mode.

(Note: If a third item overlaps even partially with the view, more than two items might be highlighted. Experimenting with the `threshold` option detailed below allows you to fine-tune this behavior).

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>

The foundation of my approach involves leveraging the Intersection Observer API to monitor intersections between list items and the lower segment of the container element. Whenever an observed item transitions from "intersecting" to "not intersecting," the API triggers my designated handler function.

To configure this setup, an object named options has been supplied to the observer with the following specifications:

  • root: denotes the list container.
  • rootMargin: establishes an offset relative to the actual bounding box of the root. This adjustment effectively decreases the target size of the root by a specified margin. In this instance, the top dimension has been reduced by 100px. An item exceeding this hypothetical boundary within the list container ceases to be categorized as "intersecting."
  • threshold: indicates the requisite portion of an element (ranging from 0 to 1.0) inside the root to qualify as "intersecting." While the default setting is 0, explicit inclusion serves clarity here. With 0 assigned, even a solitary pixel entering the root results in the element being deemed "intersecting" until the definitive departure of all pixels. Alternatively, selecting 1.0 mandates that every single pixel within the element must be within the root for classification as "intersecting." Once any pixel exits the root, it immediately shifts into a state of "not intersecting."

Subsequently, the callback responsible for executing upon alterations in intersection status among the monitored elements is established. The variable entries encapsulates the ensemble of elements undergoing transformation. Should an item now intersect the root, the corresponding CSS class is appended. Conversely, disengagement from the root prompts class removal.

Finally, the observer is instantiated and each list item is enlisted as an entry necessitating observation. This registration operation is enclosed within a function block to retain accessibility solely throughout the registration phase, after which these references become obsolete due to scoped exit.


In terms of Angular, a critical alteration is indispensable:

  • Given implementation of ngFor, enlistment of items is viable only post template rendering. Therefore, your component should interface with AfterViewInit and perform item registration alongside the observer within ngAfterViewInit().

Additionally, there exist prospective modifications:

  • Angular leverages convenient directives like @ViewChild, enhancing secure item selection from templates compared to traditional methods such as native querySelector/getElementById, particularly amidst template re-rendering instances.
  • Considering Angular's reliance on TypeScript, type declarations promote streamlined testing, error detection, and so forth. Benefitting from existing TypeScript types tailored for the Intersection Observer API alleviates manual type declaration necessities.
  • Angular harmonizes seamlessly with robust rxjs observables, offering pathways to further performance enhancement through actions like debouncing during handler function invocation, continuous observation of specific item intersection changes, etc. Numerous insightful blog posts elucidate application of the Intersection Observer API in conjunction with Angular, covering related topics comprehensively.

Access a simplified Angular adaptation via StackBlitz link, reflecting minimalistic modifications aligning with requisites.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Executing Angular async routing functions outside of the ngZone allows for more efficient

The routing function was called within a service and I am encountering the following warning message: Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'? However, I cannot simply call this.ngZone.run(...) because I ...

Is there a way to efficiently modify the positions of numerous markers on Google Maps while utilizing PhoneGap?

Hey there, I'm new to this and I have a service for tracking multiple cars. I'm using a timer to receive their locations, but I'm having trouble figuring out how to update the old marker with the new value. I've tried deleting all the m ...

What is the best method for integrating JavaScript into my Express.js application that is also utilizing Socket.IO?

In order to synchronize time for my countdown project, I am utilizing the timesync package. server.js const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); const po ...

Updating a select menu with AJAX without the need for a <div> tag within the form

Utilizing an ajax call to dynamically update a select menu is a common practice. The ajax call fetches the options from a separate .php file and populates the select menu accordingly. Below is the code snippet for inserting the dynamic content using JavaS ...

Leverage Formidable to directly stream content to Azure Blob Storage without the need to save it in the /tmp directory

An interesting example provided by Formidable can be found here: https://github.com/node-formidable/formidable/blob/master/examples/store-files-on-s3.js. It showcases how to upload a stream to an S3 bucket without saving the content to a temporary file, wh ...

Incorporating a remote PHP file into your website using JavaScript

Is it feasible to utilize JS (jQuery) for executing a $.post from any website on any domain to a script on my server? This query stems from my reluctance to disclose my PHP files to clients (and avoid spending money on ionCube or similar solutions). By se ...

Tips for managing boolean values in a JSON data structure

My JSON object is causing issues because it has True instead of true and False instead of false. How can I fix this problem? The code snippet below shows that obj2 is not working properly due to boolean values being capitalized. <!DOCTYPE html> < ...

Trigger jQuery when a breakpoint has been met

I am working on a responsive design that has multiple breakpoints. Within the content, some block dimensions are calculated using jQuery. Whenever the viewport changes, these dimensions also change and therefore the calculation needs to be re-run. How can ...

The error thrown states: "TypeError: Unable to access the property 'getFieldDecorator' of undefined in a React component."

Encountering this error: > TypeError: Cannot read property 'getFieldDecorator' of undefined > _class.render src/containers/RegisterTenant/register.js:81 78 | this.setState({ 'selectedFiles': files }); 79 | } 80 | > ...

``I'm having trouble getting the onchange event to function properly in a customized

After following a tutorial, I successfully created a custom select dropdown using the provided link. https://www.w3schools.com/howto/tryit.asp?filename=tryhow_custom_select However, I am facing an issue with the onchange event not working for the select ...

What is the best way to alternate $httpBackend when[method] declarations in unit tests to handle multiple requests?

When conducting my testing, I set up the model data and mock the response: beforeEach(function(){ var re = new RegExp(/^http\:\/\/.+?\/users-online\/(.+)$/); $httpBackend.whenGET(re).respond({id:12345, usersOnline:5000}); }) ...

The path referenced in typings is incorrect

I am currently facing an issue with my Typescript library that I am trying to publish on npmjs. It seems like the types file is not being exported correctly. The library has a simple method in the src/index.ts file and typings from src/typings/index.d.ts. ...

Unable to load class; unsure of origin for class labeled as 'cached'

Working on an Angular 10 project in visual studio code, I've encountered a strange issue. In the /app/_model/ folder, I have classes 'a', 'b', and 'c'. When running the application in MS Edge, I noticed that only classes ...

Output the initial value and subsequently disregard any values emitted during a specified time interval

Check out my code snippet: app.component.ts notifier$ = new BehaviorSubject<any>({}); notify() { this.notifier$.next({}); } app.component.html <div (scroll)="notify()"></div> <child-component [inp]="notifier$ | async" /> ...

What is the best way to create subpages within a survey?

If I want to create a survey page on the web with multiple questions, but I am facing a challenge. I do not want to have several different pages and use a "Next Button" that links to another page. I am struggling to come up with ideas on how to implement ...

Error occurred while trying to parse JSON due to an abrupt ending

While attempting to reinstall one of my old vue projects on my new computer (running Windows 10) using npm, I encountered the following error: npm ERR! Unexpected end of JSON input while parsing near '...n":"0.8.1","devDepend' ...

animations are not triggering when using ng-click inside ng-repeat, is there a reason why the event is not firing?

Check out the code in jsFiddler here After reviewing the code, it's clear that when the add button is clicked, a new item is pushed to the $scope.p. Each item in the ng-repeat loop has an event-binding and everything functions correctly. However, onc ...

Creating a Basic jQuery AJAX call

I've been struggling to make a simple jQuery AJAX script work, but unfortunately, I haven't had any success so far. Below is the code I've written in jQuery: $(document).ready(function(){ $('#doAjax').click(function(){ alert ...

Using the integrated pipeline mode in IIS is necessary for this operation to be

My aspx page has an ajax call which looks like this: $.ajax({ url: "/SiteAdmin3/UpsIntegration.aspx/addUpdatePackageData", data: JSON.stringify({ '_OrderNumber': $("#txtOrderNumber ...

How to create flexible, non-url-changing layout states with angular-ui-router?

My Objective I am working with two different styles of display: "Normal": [ Header | Body | Footer ] - primarily used for most views "Discreet": [ Body ] only, centered on screen - ideal for states like login/register, errors etc. These display styles ...