28
    Dev Log#temettu

    Figuring out in-viewport detection in React Native for ad tracking

    Ebru
    Ebru
    3 min

    Today I looked into how to properly detect when an item is actually visible on the screen in React Native, because the current banner tracking I've recently added fires on load even for components that never appear in the viewport. Not great for accurate analytics.

    I explored a few directions:

    • onLayout isn't enough; it triggers even if the item is off-screen.
    • React Native doesn’t have a built-in intersection observer like the web.
    • The likely solution is a mix of:
      • measure() / measureInWindow() to get the element’s position
      • onScroll to listen for viewport changes
      • comparing bounds to determine true visibility

    I also checked a few libraries (react-native-inviewport, react-native-intersection-observer) but they’ll need performance testing in long lists.

    It’s a small detail but for banner impressions, these details matter.

    What counts as a valid “Ad View”?

    The global standard comes from the IAB (Interactive Advertising Bureau) and like Google, Meta etc. follows as well. An ad impression should be counted only when:

    • ≥ 50% of ad visible in viewport
    • Visible for ≥ 1 continuous second
    • Not under the fold, not covered
    • App must be active (foreground)
    • Only one impression per ad load
    • Scroll or fast flicking should not count

    These prevent “fake” impressions and inflated metrics.

    Best-practice behaviour on mobile

    A valid impression must meet all:

    Condition Requirement
    Visibility At least 50% on screen
    Duration Minimum 1s visible
    Frequency Logged once per load
    Valid state App active, component actually in viewport

    AdMob and all certified SDKs do this inside the SDK.
    Since we’re using custom banners, we have to recreate the logic ourselves.

    What I implemented today

    I built everything around three core hooks opting out an external library:

    • useInView
    • useAdAnalytics
    • useCTAAnalytics

    All coordinated through the global useScrollTracking provider. The system now detects real viewport visibility using measureInWindow(), calculates visible-area percentage, enforces the ≥50% visibility rule, and ensures the ad stays visible for 1 continuous second before counting it as a valid impression.

    I also added app-state checks (foreground only), fast-scroll detection to block fake impressions, and automatic timer resets any time a condition breaks. Clicks are only logged if a valid impression exists, and everything uses a shared impression_id so multi-partner sheets can link CTA → item clicks cleanly.

    Metadata handling is now consistent (placement index/position, partner order, campaign details). Overall, the system mirrors how Google/Meta handle viewability but fully custom and built specifically for our app.

    Why this matters

    Accurate impressions → correct CTR → honest reporting → trustworthy campaigns.

    Now we can have these dashboards in Posthog with impression/click properties → automatic funnels & insights:

    1. CTR by placement
    2. CTR by campaign
    3. Top performing ads
    4. Worst placements
    5. Performance by device/platform

    I’ll turn this into a full technical case study later, but these are the core notes and base implementation from today.

    If you'd like a quiet update once a week, join our tiny letter with weekly summary of learnings, findings, and what we're building.

    No spam, just genuine updates from our indie making journey.

    Or follow the journey on: