Skip to content

Add more configuration to the viewabilityConfig #891

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
IslamRustamov opened this issue Mar 20, 2025 · 0 comments
Open

Add more configuration to the viewabilityConfig #891

IslamRustamov opened this issue Mar 20, 2025 · 0 comments

Comments

@IslamRustamov
Copy link

IslamRustamov commented Mar 20, 2025

Introduction

I want to discuss an ability to add more configuration to the viewabilityConfig, so that it's possible to more effectively control the calculations of viewable items of the VirtualizedLists.

Details

Let's bring up an example.

  1. We have a FlatList of images;
  2. We have a viewabilityConfig with itemVisiblePercentThreshold: 15;
  3. We have an absolutely positioned View above the FlatList. This View is serving a purpose of some custom header and we add paddingTop: HEADER_HEIGHT to the contentContainerStyle of the FlatList so that we see all of the list;
  4. Every time an image becomes "not visible" we want to make it slightly opaque.

Here is the result:

Image

We can see that the image AT THE BOTTOM is getting slightly opaque, when less than 15% of it is not visible. However, we can't say the same thing about the images on top of the screen. (couldn't upload gifs for some reason so bear with my screenshots)

Why is this happening? Because our header is an absolute View and the image is continuing to be visible underneath the header. And FlatList cannot correctly computate the offset from top.

In order to solve this problem, I suggest to make the next changes, which are going to allow FlatList to correctly work with absolute Views on top or below the screen:

In ViewabilityConfig (@react-native/virtualized-lists/Lists/VirtualizedList.d.ts) interface we add absoluteStartOffset and absoluteEndOffset:

export interface ViewabilityConfig {
  /**
   * Minimum amount of time (in milliseconds) that an item must be physically viewable before the
   * viewability callback will be fired. A high number means that scrolling through content without
   * stopping will not mark the content as viewable.
   */
  minimumViewTime?: number | undefined;

  /**
   * Percent of viewport that must be covered for a partially occluded item to count as
   * "viewable", 0-100. Fully visible items are always considered viewable. A value of 0 means
   * that a single pixel in the viewport makes the item viewable, and a value of 100 means that
   * an item must be either entirely visible or cover the entire viewport to count as viewable.
   */
  viewAreaCoveragePercentThreshold?: number | undefined;

  /**
   * Similar to `viewAreaCoveragePercentThreshold`, but considers the percent of the item that is visible,
   * rather than the fraction of the viewable area it covers.
   */
  itemVisiblePercentThreshold?: number | undefined;

  /**
   * Nothing is considered viewable until the user scrolls or `recordInteraction` is called after
   * render.
   */
  waitForInteraction?: boolean | undefined;

  /**
   * Offset from the start of the list
   */
  absoluteStartOffset?: number | undefined; // <-- add this

  /**
   * Offset from the end of the list
   */
  absoluteEndOffset?: number | undefined; // <-- and this
}

In computeViewableItems (@react-native/virtualized-lists/Lists/ViewabilityHelper.js) we make the next changes:

  /**
   * Determines which items are viewable based on the current metrics and config.
   */
  computeViewableItems(
    props: CellMetricProps,
    scrollOffset: number,
    viewportHeight: number,
    listMetrics: ListMetricsAggregator,
    // Optional optimization to reduce the scan size
    renderRange?: {
      first: number,
      last: number,
      ...
    },
  ): Array<number> {
...
      const absoluteStartOffset = this._config.absoluteStartOffset ?? 0; // <-- add this
      const absoluteEndOffset = this._config.absoluteEndOffset ?? 0;  // <-- add this

      const top = Math.floor(metrics.offset - scrollOffset - absoluteStartOffset);  // <-- change here
      const bottom = Math.floor(top + metrics.length);

      if (top < viewportHeight - absoluteStartOffset && bottom > 0) {  // <-- change here
        firstVisible = idx;
        if (
          _isViewable(
            viewAreaMode,
            viewablePercentThreshold,
            top,
            bottom,
            viewportHeight - absoluteStartOffset - absoluteEndOffset,  // <-- change here
            metrics.length,
          )
        ) {
          viewableIndices.push(idx);
        }

Now, if I pass my absoluteStartOffset to the viewabilityConfig - we will see the next result:

Image

We can see that both TOP and BOTTOM images are opaque, which means that viewability calculations are correct now.

We can also make it work with absolute views, located at the bottom of the screen by utilising absoluteEndOffset:

Image

You may say that it's my fault that I'm using an absolute view as a header for some reason, but there are real-life scenarios where this is being used with more complicated UI than I showed in the examples above (the reason why I suggest this change is exactly because it fixes the problem for my application which has some animations on top and rebuilding UI is not as easy as simply adding these changes).

Adding custom getItemLayout with different offset is not a solution to this problem.

This solution also works for horizontal lists.

Here is the full gist of example: https://gist.github.com/IslamRustamov/653747e65be5f59dcce953345f3dff2a

The idea of this came from https://github.com/fredrikolovsson/react-native-viewability-tracking-view

Discussion points

  1. Adding absoluteStartOffset and absoluteEndOffset to the viewabilityConfig
@IslamRustamov IslamRustamov changed the title Add Add more configuration to the viewabilityConfig Mar 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant