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.