Frontline Skirmishes, Part 1: ScrollObserver


To streamline web apps, enhance their interactivity and improve the general user experience, we more and more often look for ways to properly respond to user actions such as page scrolling. The desired behaviour of an application can be loading subsequent content as the user reaches the end of the page, “lazy loading” the images, or changing the displayed content after a certain portion of the page has been scrolled. There are at least two ways to handle this using Angular. Below I will describe the first one. It’s a classic solution based on ScrollEvent.

ScrollEvent

We can easily use the Scroll event to check the number of pixels the page has been scrolled down after each scroll.

To ensure compatibility with all browsers, it might be a good idea to use several ways to determine the page position:

It may also be worth it to refer to the event via an Observable so as to be able to use RxJs operators:

The Observable mentioned above first returns the current page position, then emits a new one each time it receives the Scroll event. The object prepared this way can serve as a backbone of the mechanism that notifies about the changing degree of visibility of the selected HTML elements as a result of page scrolling.

Visibility tracking

But how can we track the visibility of items based on the Scroll event? We can use the Observable created previously and connect it to a RxJS operator that detects the change in visibility. The code could look as follows:

The code above will provide a notification whenever the visibility of the “el” element, calculated by the isItemVisible method, changes its value after the user scrolls the page. Now we just need to define this method.

Calculating the visibility of an HTML element

To determine the visibility of an element depending on the current scroll position, we need the information about the scroll position and several other parameters:

  • browser window height in pixels
  • offset of the observed element, i.e. the position of its top in relation to the beginning of the page
  • height of the observed element

We set the values of the three parameters above as follows:

Based on these parameters, we can calculate by how many pixels the visibility of the observed element is limited from the top and from the bottom:

At this point, we can easily calculate whether the element is at least partially visible by subtracting the numbers calculated above from the total height of the element:

At this stage, we have an Observable object that we can subscribe to observe the visibility of the element as we scroll the page. There is, however, a simpler and more Angular approach, with the use of a directive.

ScrollObserver in a directive

We already have an Observable object, so we can create a simple directive that will subscribe to it at the right moment and (which is just as important) also terminate the subscription at the right moment to prevent memory leaks.

We just need to attach the above directive to the observed element so that our onScroll(visible: boolean) method receives notifications about a change in its visibility.

Code and npm

The code above is available on GitHub:

https://github.com/piotr-zysk/LabIntersectionObserver/tree/master/projects/pz-scroll-observer

Additionally, you can download it as an npm package to use in your application:

Summary

This solution allows you to monitor changes in the visibility of a selected element on the website as the user scrolls the page. It is compatible with all popular browsers, including older ones such as Internet Explorer. Please note, however, that the solution is not ideal — mainly because notifications about a change in visibility are sent only after a page scroll event occurs. Other events that may affect the visibility of the element, such as changing the size of the browser window or changing the content on the page, are ignored.

Typically, we may need a visibility change notification regardless of the reason for the change. In such a situation, the solution described above would require a significant expansion and adding quite a few complications to the logic — which is already somewhat convoluted. Doesn’t sound too good, does it? Luckily, there is another, much simpler and better way — IntersectionObserver — which I will discuss in the next post.


Piotr Zyśk

About Piotr Zyśk

Fullstack .Net/Angular developer in Atena since 2019. Previous 16 years spent on IT support for large contact center company, including development and maintenance of local infrastructure. After work I like to spend time on my hobbies, like photography, composing music on my PC or playing virtual racing games. You can also meet me biking in the nearby forests.

Leave a comment

Your email address will not be published. Required fields are marked *