kuikly-visibility-exposure

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Kuikly 曝光与可见性事件

Kuikly Exposure & Visibility Events

核心概念

Core Concepts

Kuikly 提供了一套完整的组件可见性事件系统,用于监听组件在滚动容器或页面中的可见状态变化。四大要素:
  1. 可见窗口:最近的滚动容器(Scroller/List/WaterfallList)、ModalView 或 Pager
  2. 可见性状态:四种状态 + 百分比(WILL_APPEAR → DID_APPEAR → WILL_DISAPPEAR → DID_DISAPPEAR)
  3. 触发时机:滚动偏移变化、子视图布局完成、可见区域 margin 变化、组件被移除
  4. 应用场景:曝光上报、懒加载、播放控制、可见百分比监听
Kuikly provides a complete Component Visibility Event System for monitoring changes in the visibility state of components within scroll containers or pages. Four core elements:
  1. Visible Window: The nearest scroll container (Scroller/List/WaterfallList), ModalView or Pager
  2. Visibility State: Four states + percentage (WILL_APPEAR → DID_APPEAR → WILL_DISAPPEAR → DID_DISAPPEAR)
  3. Trigger Timing: Scroll offset changes, child view layout completion, visible area margin changes, component removal
  4. Application Scenarios: Exposure reporting, lazy loading, playback control, visibility percentage monitoring

可见窗口查找规则

Visible Window Search Rules

系统从组件的 parent 开始向上遍历,找到第一个匹配的祖先作为可见窗口:
组件 → parent → ... → ScrollerView(Scroller/List/WaterfallList/PageList)→ 命中
                     → ModalView → 命中
                     → Pager → 命中(兜底)
  • 对于 ScrollerView(Scroller/List/WaterfallList):可见性基于列表可见区域计算,会减去滚动偏移量
  • 对于 ModalView:可见性基于 Modal 的布局 frame 计算
  • 对于 Pager:可见性基于页面的布局 frame 计算
重要:可见性是相对于最近滚动容器的可见区域,而非屏幕位置。若需屏幕位置可见性,如果嵌套滚动容器场景,可使用外层滚动容器的 scroll 事件回调,结合列表偏移量计算节点真实位置。
The system traverses upwards from the component's parent to find the first matching ancestor as the visible window:
Component → parent → ... → ScrollerView(Scroller/List/WaterfallList/PageList)→ Hit
                     → ModalView → Hit
                     → Pager → Hit (fallback)
  • For ScrollerView (Scroller/List/WaterfallList): Visibility is calculated based on the list visible area, minus the scroll offset
  • For ModalView: Visibility is calculated based on the Modal's layout frame
  • For Pager: Visibility is calculated based on the page's layout frame
Important: Visibility is relative to the visible area of the nearest scroll container, not the screen position. If screen position visibility is required, especially in nested scroll container scenarios, you can use the scroll event callback of the outer scroll container and calculate the real position of the node combined with the list offset.

状态机

State Machine

                    ┌──────────────────────────────────────────────┐
                    │                                              │
                    ▼                                              │
  DID_DISAPPEAR ──→ WILL_APPEAR ──→ DID_APPEAR ──→ WILL_DISAPPEAR ─┘
  (完全不可见)       (部分可见)       (完全可见)       (部分可见)
三种空间关系与状态转换:
空间关系判定条件状态转换
相离(组件完全在窗口外)组件 frame 与窗口无交集→ WILL_DISAPPEAR → DID_DISAPPEAR
包含(组件完全在窗口内)组件 frame 完全在窗口内→ WILL_APPEAR → DID_APPEAR
相交(组件部分在窗口内)组件 frame 与窗口部分重叠从 DID_DISAPPEAR → WILL_APPEAR;从 DID_APPEAR → WILL_DISAPPEAR
特殊行为:
  • 组件被移除(v-if 移除、页面销毁等)时,会自动触发 WILL_DISAPPEAR → DID_DISAPPEAR,并将 percentage 置为 0
  • 状态变更是异步的(通过
    addNextTickTask
    调度),页面销毁时除外(同步执行)

                    ┌──────────────────────────────────────────────┐
                    │                                              │
                    ▼                                              │
  DID_DISAPPEAR ──→ WILL_APPEAR ──→ DID_APPEAR ──→ WILL_DISAPPEAR ─┘
  (Completely Invisible)       (Partially Visible)       (Fully Visible)       (Partially Visible)
Three Spatial Relationships and State Transitions:
Spatial RelationshipJudgment ConditionState Transition
Disjoint (Component is completely outside the window)No intersection between component frame and window→ WILL_DISAPPEAR → DID_DISAPPEAR
Contained (Component is completely inside the window)Component frame is fully within the window→ WILL_APPEAR → DID_APPEAR
Intersecting (Component is partially inside the window)Partial overlap between component frame and windowFrom DID_DISAPPEAR → WILL_APPEAR; From DID_APPEAR → WILL_DISAPPEAR
Special Behaviors:
  • When a component is removed (removed via v-if, page destroyed, etc.), it will automatically trigger WILL_DISAPPEAR → DID_DISAPPEAR, and set percentage to 0
  • State changes are asynchronous (scheduled via
    addNextTickTask
    ), except when the page is destroyed (executed synchronously)

API 速查

API Quick Reference

可见性事件

Visibility Events

事件描述回调参数import
willAppear { }
组件将要可见(部分进入窗口)
com.tencent.kuikly.core.base.event.willAppear
didAppear { }
组件完全可见(完全在窗口内)
com.tencent.kuikly.core.base.event.didAppear
willDisappear { }
组件将要不可见(部分离开窗口)
com.tencent.kuikly.core.base.event.willDisappear
didDisappear { }
组件完全不可见(完全在窗口外)
com.tencent.kuikly.core.base.event.didDisappear
appearPercentage { }
组件可见百分比变化
percentage01: Float
([0,1])
com.tencent.kuikly.core.base.event.appearPercentage
EventDescriptionCallback Parametersimport
willAppear { }
Component is about to become visible (partially enters the window)None
com.tencent.kuikly.core.base.event.willAppear
didAppear { }
Component is fully visible (completely inside the window)None
com.tencent.kuikly.core.base.event.didAppear
willDisappear { }
Component is about to become invisible (partially leaves the window)None
com.tencent.kuikly.core.base.event.willDisappear
didDisappear { }
Component is completely invisible (completely outside the window)None
com.tencent.kuikly.core.base.event.didDisappear
appearPercentage { }
Component visibility percentage changes
percentage01: Float
([0,1])
com.tencent.kuikly.core.base.event.appearPercentage

appearPercentage 百分比计算逻辑

appearPercentage Calculation Logic

空间关系percentage 值
完全不可见(相离)0
完全可见(包含)1
部分可见(相交)纵向列表:
可见高度 / 组件高度
;横向列表(FlexDirection.ROW / ROW_REVERSE):
可见宽度 / 组件宽度
percentage 值始终被 clamp 到 [0, 1] 范围内。只有当 percentage 发生变化时才会触发回调。
Spatial Relationshippercentage Value
Completely Invisible (Disjoint)0
Fully Visible (Contained)1
Partially Visible (Intersecting)Vertical List:
Visible Height / Component Height
; Horizontal List (FlexDirection.ROW / ROW_REVERSE):
Visible Width / Component Width
The percentage value is always clamped to the [0, 1] range. The callback is only triggered when the percentage changes.

Scroller/List/WaterfallList 辅助属性

Scroller/List/WaterfallList Auxiliary Properties

属性描述参数类型支持的容器
visibleAreaIgnoreTopMargin(margin)
从可见窗口顶部裁掉指定高度FloatScroller、List、WaterfallList
visibleAreaIgnoreBottomMargin(margin)
从可见窗口底部裁掉指定高度FloatScroller、List、WaterfallList
作用原理:缩小列表的"可见性判定窗口"。设置后,可见窗口高度 = 列表高度 - marginTop - marginBottom,子组件 Y 坐标也会减去 marginTop。例如列表高度 500dp,设置
visibleAreaIgnoreTopMargin(60f)
visibleAreaIgnoreBottomMargin(80f)
后,只有列表中间 360dp 区域内的子组件才会被判定为可见。
典型场景:列表顶部/底部有固定遮挡 UI(如悬浮导航栏、半透明蒙层、底部工具栏),被遮挡区域内的子组件虽然在列表坐标系内"可见",但实际上用户看不到,需要从曝光判定中排除。

PropertyDescriptionParameter TypeSupported Containers
visibleAreaIgnoreTopMargin(margin)
Cut off the specified height from the top of the visible windowFloatScroller, List, WaterfallList
visibleAreaIgnoreBottomMargin(margin)
Cut off the specified height from the bottom of the visible windowFloatScroller, List, WaterfallList
Working Principle: Reduce the "visibility judgment window" of the list. After setting, visible window height = list height - marginTop - marginBottom, and the child component Y coordinate will also subtract marginTop. For example, if the list height is 500dp, after setting
visibleAreaIgnoreTopMargin(60f)
and
visibleAreaIgnoreBottomMargin(80f)
, only child components within the middle 360dp area of the list will be judged as visible.
Typical Scenario: Fixed occluding UI at the top/bottom of the list (such as floating navigation bar, translucent mask, bottom toolbar). Although child components in the occluded area are "visible" in the list coordinate system, users cannot actually see them, so they need to be excluded from exposure judgment.

快速入门

Quick Start

didAppear 曝光上报

didAppear Exposure Reporting

kotlin
import com.tencent.kuikly.core.base.event.didAppear

View {
    attr {
        size(100f, 100f)
        backgroundColor(Color.GREEN)
    }
    event {
        didAppear {
            // 组件完全可见,执行曝光上报
        }
    }
}
kotlin
import com.tencent.kuikly.core.base.event.didAppear

View {
    attr {
        size(100f, 100f)
        backgroundColor(Color.GREEN)
    }
    event {
        didAppear {
            // Component is fully visible, execute exposure reporting
        }
    }
}

appearPercentage 可见百分比监听

appearPercentage Visibility Percentage Monitoring

kotlin
import com.tencent.kuikly.core.base.event.appearPercentage

View {
    attr {
        size(100f, 100f)
        backgroundColor(Color.GREEN)
    }
    event {
        appearPercentage { percentage01 ->
            // percentage01 为 [0,1] 的露出百分比
            // 1 表示 100% 可见,0 表示 0% 可见
            if (percentage01 >= 0.5f) {
                // 超过 50% 可见时执行逻辑
            }
        }
    }
}
kotlin
import com.tencent.kuikly.core.base.event.appearPercentage

View {
    attr {
        size(100f, 100f)
        backgroundColor(Color.GREEN)
    }
    event {
        appearPercentage { percentage01 ->
            // percentage01 is the visibility percentage in [0,1]
            // 1 means 100% visible, 0 means 0% visible
            if (percentage01 >= 0.5f) {
                // Execute logic when visibility exceeds 50%
            }
        }
    }
}

完整生命周期监听

Complete Lifecycle Monitoring

kotlin
import com.tencent.kuikly.core.base.event.willAppear
import com.tencent.kuikly.core.base.event.didAppear
import com.tencent.kuikly.core.base.event.willDisappear
import com.tencent.kuikly.core.base.event.didDisappear
import com.tencent.kuikly.core.base.event.appearPercentage

View {
    event {
        willAppear {
            // 组件将要可见(部分进入窗口)
        }
        didAppear {
            // 组件完全可见 → 曝光上报
        }
        willDisappear {
            // 组件将要不可见(部分离开窗口)
        }
        didDisappear {
            // 组件完全不可见
        }
        appearPercentage { percentage ->
            // 可见百分比变化
        }
    }
}
kotlin
import com.tencent.kuikly.core.base.event.willAppear
import com.tencent.kuikly.core.base.event.didAppear
import com.tencent.kuikly.core.base.event.willDisappear
import com.tencent.kuikly.core.base.event.didDisappear
import com.tencent.kuikly.core.base.event.appearPercentage

View {
    event {
        willAppear {
            // Component is about to become visible (partially enters the window)
        }
        didAppear {
            // Component is fully visible → Execute exposure reporting
        }
        willDisappear {
            // Component is about to become invisible (partially leaves the window)
        }
        didDisappear {
            // Component is completely invisible
        }
        appearPercentage { percentage ->
            // Visibility percentage changes
        }
    }
}

visibleAreaIgnoreMargin 排除遮挡区域

visibleAreaIgnoreMargin Exclude Occluded Areas

kotlin
import com.tencent.kuikly.core.base.event.didAppear

Scroller {
    attr {
        flex(1f)
        // 顶部 60dp 区域内的子组件不参与曝光判定
        visibleAreaIgnoreTopMargin(60f)
        // 底部 80dp 区域内的子组件不参与曝光判定
        visibleAreaIgnoreBottomMargin(80f)
    }

    View {
        attr {
            height(120f)
            backgroundColor(Color(0xFFE3F2FD))
        }
        event {
            didAppear {
                // 只有完全进入中间有效区域(500 - 60 - 80 = 360dp)时才触发
            }
        }
    }
}

kotlin
import com.tencent.kuikly.core.base.event.didAppear

Scroller {
    attr {
        flex(1f)
        // Child components in the top 60dp area do not participate in exposure judgment
        visibleAreaIgnoreTopMargin(60f)
        // Child components in the bottom 80dp area do not participate in exposure judgment
        visibleAreaIgnoreBottomMargin(80f)
    }

    View {
        attr {
            height(120f)
            backgroundColor(Color(0xFFE3F2FD))
        }
        event {
            didAppear {
                // Only triggered when fully entering the middle valid area (500 - 60 - 80 = 360dp)
            }
        }
    }
}

常见错误与陷阱

Common Mistakes & Pitfalls

错误做法正确做法原因
❌ 根节点使用
didAppear
监听页面曝光
✅ 使用页面生命周期
pageDidAppear()
根节点没有滚动容器作为可见窗口,行为不可预期
❌ 认为可见性是相对于屏幕位置✅ 可见性是相对于最近滚动容器的可见区域需要屏幕位置请结合 scroll 事件偏移量计算
❌ 在
appearPercentage
中直接执行曝光上报
✅ 曝光上报用
didAppear
,或加阈值 + 去重标记
appearPercentage
每次滚动都触发,频率极高
❌ 忘记 import 可见性事件扩展函数✅ 添加对应的 import 语句这些是
Event
的扩展函数,需要显式 import

Wrong PracticeCorrect PracticeReason
❌ Use
didAppear
on root node to monitor page exposure
✅ Use page lifecycle
pageDidAppear()
The root node has no scroll container as visible window, behavior is unpredictable
❌ Assume visibility is relative to screen position✅ Visibility is relative to the visible area of the nearest scroll containerFor screen position visibility, calculate with scroll event offset
❌ Directly execute exposure reporting in
appearPercentage
✅ Use
didAppear
for exposure reporting, or add threshold + deduplication mark
appearPercentage
is triggered on every scroll, which is extremely frequent
❌ Forget to import visibility event extension functions✅ Add corresponding import statementsThese are extension functions of
Event
, explicit import is required

完整使用案例

Complete Usage Examples

EXAMPLES.md
See EXAMPLES.md