Skip to content

Commit

Permalink
Support rn_rootThreshold on IntersectionObserver (viewAreaCoverageP…
Browse files Browse the repository at this point in the history
…ercentThreshold)

Summary:
Add support for `rn_rootThreshold`.

`rn_rootThreshold` is a custom IntersectionObserver option and not part of the IntersectionObserver spec. We are adding it because it covers a specific use-case for measuring viewability that is robust for `target`s that are larger than the viewport or specified `root`.

The threshold ratio is of the intersection area (of `root` and `target`) to the total area of the `root`. 


 {F1960832959} 
Source - EX314979

`rn_rootThreshold` is an optional threshold and can be combined with the `thresholds` option. An intersection will fire if any specified thresholds is met.

Note: If you use specify a `rn_rootThreshold`, the default `threshold` is no longer applied

The main use case of `rn_rootThreshold` is being able to specify a level of viewability independent of `target` size. For example, a `target` that is larger than the `root` (commonly the viewport) will not trigger the IntersectionObserver for a `threshold` of `1`. Setting `rn_rootThreshold` of `1`, will trigger once the item takes full size of the `root`.';

Reviewed By: yungsters

Differential Revision: D66031119
  • Loading branch information
lunaleaps authored and facebook-github-bot committed Nov 22, 2024
1 parent aec7a66 commit 02fac84
Show file tree
Hide file tree
Showing 15 changed files with 431 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,15 @@ void NativeIntersectionObserver::observe(
auto shadowNode =
shadowNodeFromValue(runtime, std::move(options.targetShadowNode));
auto thresholds = options.thresholds;
auto rootThresholds = options.rootThresholds;
auto& uiManager = getUIManagerFromRuntime(runtime);

intersectionObserverManager_.observe(
intersectionObserverId, shadowNode, thresholds, uiManager);
intersectionObserverId,
shadowNode,
thresholds,
rootThresholds,
uiManager);
}

void NativeIntersectionObserver::unobserve(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ using NativeIntersectionObserverObserveOptions =
// targetShadowNode
jsi::Object,
// thresholds
std::vector<Float>>;
std::vector<Float>,
// rootThresholds
std::optional<std::vector<Float>>>;

template <>
struct Bridging<NativeIntersectionObserverObserveOptions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ namespace facebook::react {
IntersectionObserver::IntersectionObserver(
IntersectionObserverObserverId intersectionObserverId,
ShadowNode::Shared targetShadowNode,
std::vector<Float> thresholds)
std::vector<Float> thresholds,
std::optional<std::vector<Float>> rootThresholds)
: intersectionObserverId_(intersectionObserverId),
targetShadowNode_(std::move(targetShadowNode)),
thresholds_(std::move(thresholds)) {}
thresholds_(std::move(thresholds)),
rootThresholds_(std::move(rootThresholds)) {}

static Rect getRootBoundingRect(
const LayoutableShadowNode& layoutableRootShadowNode) {
Expand Down Expand Up @@ -85,6 +87,18 @@ static Rect computeIntersection(
return Rect::intersect(rootBoundingRect, clippedTargetBoundingRect);
}

static Float getHighestThresholdCrossed(
Float intersectionRatio,
const std::vector<Float>& thresholds) {
Float highestThreshold = -1.0f;
for (auto threshold : thresholds) {
if (intersectionRatio >= threshold) {
highestThreshold = threshold;
}
}
return highestThreshold;
}

// Partially equivalent to
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
std::optional<IntersectionObserverEntry>
Expand Down Expand Up @@ -124,8 +138,22 @@ IntersectionObserver::updateIntersectionObservation(
return setNotIntersectingState(rootBoundingRect, targetBoundingRect, time);
}

auto highestThresholdCrossed = getHighestThresholdCrossed(intersectionRatio);
if (highestThresholdCrossed == -1) {
auto highestThresholdCrossed =
getHighestThresholdCrossed(intersectionRatio, thresholds_);

auto highestRootThresholdCrossed = -1.0f;
if (rootThresholds_.has_value()) {
Float rootBoundingRectArea =
rootBoundingRect.size.width * rootBoundingRect.size.height;
Float rootThresholdIntersectionRatio = rootBoundingRectArea == 0
? 0
: intersectionRectArea / rootBoundingRectArea;
highestRootThresholdCrossed = getHighestThresholdCrossed(
rootThresholdIntersectionRatio, rootThresholds_.value());
}

if (highestThresholdCrossed == -1.0f &&
highestRootThresholdCrossed == -1.0f) {
return setNotIntersectingState(rootBoundingRect, targetBoundingRect, time);
}

Expand All @@ -134,6 +162,7 @@ IntersectionObserver::updateIntersectionObservation(
targetBoundingRect,
intersectionRect,
highestThresholdCrossed,
highestRootThresholdCrossed,
time);
}

Expand All @@ -143,25 +172,16 @@ IntersectionObserver::updateIntersectionObservationForSurfaceUnmount(
return setNotIntersectingState(Rect{}, Rect{}, time);
}

Float IntersectionObserver::getHighestThresholdCrossed(
Float intersectionRatio) {
Float highestThreshold = -1;
for (auto threshold : thresholds_) {
if (intersectionRatio >= threshold) {
highestThreshold = threshold;
}
}
return highestThreshold;
}

std::optional<IntersectionObserverEntry>
IntersectionObserver::setIntersectingState(
const Rect& rootBoundingRect,
const Rect& targetBoundingRect,
const Rect& intersectionRect,
Float threshold,
Float rootThreshold,
double time) {
auto newState = IntersectionObserverState::Intersecting(threshold);
auto newState =
IntersectionObserverState::Intersecting(threshold, rootThreshold);

if (state_ != newState) {
state_ = newState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class IntersectionObserver {
IntersectionObserver(
IntersectionObserverObserverId intersectionObserverId,
ShadowNode::Shared targetShadowNode,
std::vector<Float> thresholds);
std::vector<Float> thresholds,
std::optional<std::vector<Float>> rootThresholds = std::nullopt);

// Partially equivalent to
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
Expand All @@ -59,13 +60,12 @@ class IntersectionObserver {
}

private:
Float getHighestThresholdCrossed(Float intersectionRatio);

std::optional<IntersectionObserverEntry> setIntersectingState(
const Rect& rootBoundingRect,
const Rect& targetBoundingRect,
const Rect& intersectionRect,
Float threshold,
Float rootThreshold,
double time);

std::optional<IntersectionObserverEntry> setNotIntersectingState(
Expand All @@ -76,6 +76,7 @@ class IntersectionObserver {
IntersectionObserverObserverId intersectionObserverId_;
ShadowNode::Shared targetShadowNode_;
std::vector<Float> thresholds_;
std::optional<std::vector<Float>> rootThresholds_;
mutable IntersectionObserverState state_ =
IntersectionObserverState::Initial();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ void IntersectionObserverManager::observe(
IntersectionObserverObserverId intersectionObserverId,
const ShadowNode::Shared& shadowNode,
std::vector<Float> thresholds,
std::optional<std::vector<Float>> rootThresholds,
const UIManager& uiManager) {
SystraceSection s("IntersectionObserverManager::observe");

Expand All @@ -34,7 +35,10 @@ void IntersectionObserverManager::observe(

auto& observers = observersBySurfaceId_[surfaceId];
observers.emplace_back(IntersectionObserver{
intersectionObserverId, shadowNode, std::move(thresholds)});
intersectionObserverId,
shadowNode,
std::move(thresholds),
std::move(rootThresholds)});
observer = &observers.back();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class IntersectionObserverManager final : public UIManagerMountHook {
IntersectionObserverObserverId intersectionObserverId,
const ShadowNode::Shared& shadowNode,
std::vector<Float> thresholds,
std::optional<std::vector<Float>> rootThresholds,
const UIManager& uiManager);

void unobserve(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ IntersectionObserverState IntersectionObserverState::NotIntersecting() {
}

IntersectionObserverState IntersectionObserverState::Intersecting(
Float threshold) {
return IntersectionObserverState(
IntersectionObserverStateType::Intersecting, threshold);
Float threshold,
Float rootThreshold) {
return {
IntersectionObserverStateType::Intersecting, threshold, rootThreshold};
}

IntersectionObserverState::IntersectionObserverState(
Expand All @@ -33,8 +34,9 @@ IntersectionObserverState::IntersectionObserverState(

IntersectionObserverState::IntersectionObserverState(
IntersectionObserverStateType state,
Float threshold)
: state_(state), threshold_(threshold) {}
Float threshold,
Float rootThreshold)
: state_(state), threshold_(threshold), rootThreshold_(rootThreshold) {}

bool IntersectionObserverState::isIntersecting() const {
return state_ == IntersectionObserverStateType::Intersecting;
Expand All @@ -50,7 +52,8 @@ bool IntersectionObserverState::operator==(
return true;
}

return threshold_ == other.threshold_;
return threshold_ == other.threshold_ &&
rootThreshold_ == other.rootThreshold_;
}

bool IntersectionObserverState::operator!=(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#pragma once

#include <react/renderer/graphics/Float.h>
#include <optional>

namespace {
enum class IntersectionObserverStateType {
Expand All @@ -24,6 +25,9 @@ class IntersectionObserverState {
static IntersectionObserverState Initial();
static IntersectionObserverState NotIntersecting();
static IntersectionObserverState Intersecting(Float threshold);
static IntersectionObserverState Intersecting(
Float threshold,
Float rootThreshold);

bool isIntersecting() const;

Expand All @@ -32,15 +36,21 @@ class IntersectionObserverState {

private:
explicit IntersectionObserverState(IntersectionObserverStateType state);

IntersectionObserverState(
IntersectionObserverStateType state,
Float threshold);
Float threshold,
Float rootThreshold);

IntersectionObserverStateType state_;

// This value is only relevant if the state is
// IntersectionObserverStateType::Intersecting.
Float threshold_{};

// This value is only relevant if the state is
// IntersectionObserverStateType::Intersecting.
std::optional<Float> rootThreshold_{};
};

} // namespace facebook::react
Loading

0 comments on commit 02fac84

Please sign in to comment.