Skip to content

노출 감지

지원환경: React NativeReact Native SDKv1.0.3
실행환경: Toss AppSandbox App

스크롤 뷰나 리스트에서 특정 요소가 화면에 보이는지 여부를 감지할 수 있어요.

노출 감지하기

SDK 컴포넌트: InView

InView 컴포넌트는 화면에 요소가 보이기 시작하거나 사라지는 것을 감지하는 컴포넌트예요. 요소가 화면에 조금이라도 보이기 시작하면 onChanged 핸들러가 호출되고 첫 번째 인자로 true 값이 전달돼요. 반대로 요소가 화면에서 사라지면 false 값이 전달돼요. onChanged 핸들러의 두 번째 인자로 요소의 화면 노출 비율이 전달돼요. 노출 비율 값은 0에서 1.0 사이예요. 예를 들어 0.2가 전달되면 컴포넌트가 20%만큼 화면에 노출된 상태라는 의미예요.

유의하세요

InView는 반드시 IOContext가 포함된 IOScrollView 또는 IOFlatList 내부에서 사용해야 해요. 만약 IOContext 외부에서 사용하면 IOProviderMissingError가 발생해요.

시그니처

typescript
class InView<T = ViewProps> extends PureComponent<InViewProps<T>> {
  static contextType: import('react').Context<IOContextValue>;
  static defaultProps: Partial<InViewProps>;
  context: undefined | IOContextValue;
  mounted: boolean;
  protected element: Element;
  protected instance: undefined | ObserverInstance;
  protected view: any;
  constructor(props: InViewProps<T>);
  componentDidMount(): void;
  componentWillUnmount(): void;
  protected handleChange: (inView: boolean, areaThreshold: number) => void;
  protected handleRef: (ref: any) => void;
  protected handleLayout: (event: LayoutChangeEvent) => void;
  measure: (...args: any) => void;
  measureInWindow: (...args: any) => void;
  measureLayout: (...args: any) => void;
  setNativeProps: (...args: any) => void;
  focus: (...args: any) => void;
  blur: (...args: any) => void;
  render(): import('react/jsx-runtime').JSX.Element | null;
}

파라미터

  • props필수 · Object

    컴포넌트에 전달되는 props 객체예요.

    • props.children필수 · React.ReactNode

      컴포넌트 하위에 렌더링될 자식 컴포넌트들이에요.

  • prop.asReact.ComponentType · View

    실제 렌더링할 컴포넌트를 지정해요. 기본값은 View 컴포넌트예요.

  • triggerOnceboolean · false

    요소가 화면에 처음 보일 때 한 번만 onChange 콜백을 호출하려면 이 옵션을 사용해요.

  • onLayout(event: LayoutChangeEvent) => void

    레이아웃에 변경이 생겼을 때 호출되는 콜백 함수예요.

  • onChange(inView: boolean, areaThreshold: number) => void

    요소가 화면에 나타나거나 사라질 때 호출되는 콜백 함수예요. 첫 번째 인자로 노출 여부가, 두 번째 인자로 노출 비율이 전달돼요.

예제

tsx
import { LayoutChangeEvent, View, Text, Dimensions } from 'react-native';
import { InView, IOScrollView } from '@granite-js/react-native';

function InViewExample() {
  const handleLayout = (event: LayoutChangeEvent) => {
    console.log('레이아웃 변경됨', event.nativeEvent.layout);
  };

  const handleChange = (inView: boolean, areaThreshold: number) => {
    if (inView) {
      console.log(`${areaThreshold * 100}% 비율만큼 화면에 보이는 상태`);
    } else {
      console.log('화면에 보이지 않는 상태');
    }
  };

  return (
    <IOScrollView>
      <View style={{ height: HEIGHT, width: '100%', backgroundColor: 'blue' }}>
        <Text style={{ color: 'white' }}>스크롤을 내려주세요</Text>
      </View>
      <InView onLayout={handleLayout} onChange={handleChange}>
        <View style={{ width: 100, height: 300, backgroundColor: 'yellow' }}>
          <View
            style={{
              position: 'absolute',
              top: 30,
              width: 100,
              height: 1,
              borderWidth: 1,
            }}
          >
            <Text style={{ position: 'absolute', top: 0 }}>10% 지점</Text>
          </View>
        </View>
      </InView>
    </IOScrollView>
  );
}

리스트 노출 감지하기

SDK 컴포넌트: IOFlatList

IOFlatList는 스크롤 중 특정 요소가 화면에 보이거나 사라지는지를 감지하기 위해 Intersection Observer 기능을 추가한 FlatList 컴포넌트예요. 이 컴포넌트를 사용하면 리스트의 각 항목이 화면에 나타나는지 여부를 쉽게 확인하고 처리할 수 있어요.

InView와 함께 사용하면 각 요소의 노출 상태를 확인할 수 있어요. 자식 요소로 포함된 InView 컴포넌트는 IOFlatList의 관찰 기능을 통해 요소가 화면에 보이는지 여부를 감지하고, 노출 상태에 따라 이벤트를 발생시켜요.

시그니처

typescript
IOFlatList: typeof IOFlatListFunction;

예제

tsx
import { ReactNode, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { InView, IOFlatList } from '@granite-js/react-native';

const mockData = Array.from({ length: 30 }, (_, i) => ({ key: String(i) }));

function FlatListPage() {
  return <IOFlatList data={mockData} renderItem={({ item }) => <InViewItem>{item.key}</InViewItem>} />;
}

function InViewItem({ children }: { children: ReactNode }) {
  const [visible, setVisible] = useState(false);

  return (
    <InView onChange={setVisible}>
      <View style={styles.item}>
        <Text>{children}</Text>
        <Text>{visible ? 'visible' : ''}</Text>
      </View>
    </InView>
  );
}

const styles = StyleSheet.create({
  item: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#ddd',
  },
});

스크롤 영역 노출 감지하기

SDK 컴포넌트: IOScrollView, ImpressionArea

IOScrollViewImpressionArea를 사용해서 스크롤 뷰 내에서 요소가 화면에 보이는지 확인할 수 있어요. 특정 요소가 화면에 일정 비율 이상 나타나면 onImpressionStart 콜백이 호출돼요.

ImpressionAreaareaThreshold 값을 설정하면, 설정한 비율 이상으로 요소가 보이면 onImpressionStart 콜백이 호출돼요.

IOScrollView 내부에서만 사용할 수 있어요

ImpressionArea는 반드시 IOScrollView 내부에 있어야 해요.

그렇지 않으면, IOContext.Provider 밖에서 사용되었습니다.라는 에러가 발생해요.

스크롤 뷰에서 요소가 20% 이상 나타날 때 처리하기

다음 코드는 높이 100px을 가진 요소가 IOScrollView에서 20%이상 나타났을 때onImpressionStart가 호출되는 예제에요.

빨간색 선은 100px20% 지점을 시각적으로 표시한 예시예요.

tsx
import { createRoute, ImpressionArea, IOScrollView } from '@granite-js/react-native';
import { ReactNode } from 'react';
import { Alert, Text, View } from 'react-native';

export const Route = createRoute('/image', {
  component: Image,
});

/* 스크롤을 위한 Dummy 콘텐츠 */
const dummies = new Array(10).fill(undefined);

/** 20% 지점 */
const AREA_THRESHOLD = 0.2; 

function Image() {
  return (
    <IOScrollView> // [!code focus]
      {dummies.map((_, index) => {
        return <DummyContent key={index} text={10 - index} />;
      })}
      <ImpressionArea
        areaThreshold={AREA_THRESHOLD} 
        onImpressionStart={() => { 
          Alert.alert('Impression Start'); 
        }} 
      > // [!code focus]
        <View
          style={{
            width: '100%',
            height: 100,
            backgroundColor: 'blue',
          }}
        >
          <DebugLine areaThreshold={AREA_THRESHOLD} />
        </View>
      </ImpressionArea> // [!code focus]
    </IOScrollView> 
  );
}

/** 비율을 시각적으로 표시하는 디버그 컴포넌트 */
function DebugLine({ areaThreshold }: { areaThreshold: number }) {
  return (
    <View
      style={{
        position: 'absolute',
        top: `${areaThreshold * 100}%`,
        width: '100%',
        height: 1,
        backgroundColor: 'red',
      }}
    />
  );
}

/** Dummy 영역 */
function DummyContent({ text }: { text: ReactNode }) {
  return (
    <View
      style={{
        width: '100%',
        height: 100,
        borderWidth: 1,
      }}
    >
      <Text>{text}</Text>
    </View>
  );
}