> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adrop.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 배너 광고

> React에서 배너 광고를 구현하는 방법을 안내합니다.

## 구현 방법

배너 광고는 두 가지 방식으로 구현할 수 있습니다.

| 방식              | 설명                 | 추천          |
| --------------- | ------------------ | ----------- |
| Data Attributes | HTML 속성만으로 간단하게 구현 | 빠른 구현       |
| renderAd        | 광고 로드 타이밍을 직접 제어   | 세밀한 제어 필요 시 |

<Note>
  개발 환경에서는 테스트 유닛 ID를 사용하세요: `PUBLIC_TEST_UNIT_ID_320_100`
</Note>

***

## 1. Data Attributes 방식

가장 간단한 방식으로, `data-adrop-unit` 속성만 추가하면 SDK가 자동으로 광고를 로드합니다.

```tsx theme={null}
function BannerAd() {
  return (
    <div
      data-adrop-unit="YOUR_UNIT_ID"
      data-adrop-context-id="YOUR_CONTEXT_ID"
      data-adrop-theme="light"
      data-adrop-custom-click="true"  // 커스텀 클릭 처리 활성화
    />
  );
}
```

### Data Attributes

<ParamField body="data-adrop-unit" type="string" required>
  애드컨트롤 콘솔에서 생성한 유닛 ID
</ParamField>

<ParamField body="data-adrop-context-id" type="string">
  [문맥 타겟팅](/ko/sdk/web/targeting#문맥-타겟팅)용 Context ID
</ParamField>

<ParamField body="data-adrop-theme" type="string" default="light">
  테마 설정 (`light` 또는 `dark`) - [다크모드 설정](/ko/ad-unit/dark-mode) 참고
</ParamField>

<ParamField body="data-adrop-custom-click" type="boolean" default={false}>
  커스텀 클릭 처리 활성화 - `true`로 설정하면 SDK가 자동으로 목적지 URL로 이동하지 않고, `adClicked` 이벤트만 발생시킵니다. 개발자가 클릭 동작을 직접 구현할 수 있습니다.
</ParamField>

<ParamField body="data-adrop-backfill-mode" type="string" default="responsive">
  백필 광고 렌더링 모드 (`responsive` 또는 `fixed`). 자세한 내용은 [백필 렌더링 모드](/ko/sdk/web/backfill) 가이드를 참고하세요. (SDK 1.2.3+)
</ParamField>

<ParamField body="data-adrop-ad-id" type="string">
  광고 식별자 — **내 기기에서 보기** 기능으로 등록한 테스트 기기에서 소재를 미리 확인할 때 필수입니다. 운영 환경에 하드코딩하지 마세요.
</ParamField>

### 광고 재요청

React의 key를 변경하여 컴포넌트를 리마운트하면 광고가 다시 요청됩니다.

```tsx theme={null}
function App() {
  const [adKey, setAdKey] = useState(0);

  const reloadAd = () => {
    setAdKey(prev => prev + 1);
  };

  return (
    <>
      <div key={adKey} data-adrop-unit="YOUR_UNIT_ID" />
      <button onClick={reloadAd}>광고 새로고침</button>
    </>
  );
}
```

***

## 2. renderAd 방식

광고 로드 타이밍을 직접 제어하고 싶을 때 사용합니다.

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function BannerAd() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, {
        unit: 'YOUR_UNIT_ID',
        contextId: 'YOUR_CONTEXT_ID',
        theme: 'light',
        useCustomClick: true  // 커스텀 클릭 처리 활성화
      });
    }

  }, []);

  return <div ref={containerRef} />;
}
```

### renderAd 옵션

<ParamField body="unit" type="string" required>
  애드컨트롤 콘솔에서 생성한 유닛 ID
</ParamField>

<ParamField body="uid" type="string">
  사용자 식별자 (SDK 설정 값을 개별 지면에서 오버라이드할 때 사용)
</ParamField>

<ParamField body="contextId" type="string">
  [문맥 타겟팅](/ko/sdk/web/targeting#문맥-타겟팅)용 Context ID
</ParamField>

<ParamField body="theme" type="string" default="light">
  테마 설정 (`light` 또는 `dark`) - [다크모드 설정](/ko/ad-unit/dark-mode) 참고
</ParamField>

<ParamField body="useCustomClick" type="boolean" default={false}>
  커스텀 클릭 처리 활성화 - `true`로 설정하면 SDK가 자동으로 목적지 URL로 이동하지 않고, `adClicked` 이벤트만 발생시킵니다. 개발자가 클릭 동작을 직접 구현할 수 있습니다.
</ParamField>

<ParamField body="backfillMode" type="'responsive' | 'fixed'" default="responsive">
  백필 광고 렌더링 모드. 자세한 내용은 [백필 렌더링 모드](/ko/sdk/web/backfill) 가이드를 참고하세요. (SDK 1.2.3+)
</ParamField>

<ParamField body="adId" type="string">
  광고 식별자 — **내 기기에서 보기** 기능으로 등록한 테스트 기기에서 소재를 미리 확인할 때 필수입니다. 운영 환경에 하드코딩하지 마세요.
</ParamField>

### 광고 재요청

`renderAd()`를 다시 호출하면 광고가 재요청됩니다. Data Attributes로 생성한 지면에도 사용할 수 있습니다.

```tsx theme={null}
function BannerAd() {
  const containerRef = useRef<HTMLDivElement>(null);

  const reloadAd = () => {
    const adrop = Adrop.instance();
    if (containerRef.current) {
      adrop.renderAd(containerRef.current, {
        unit: 'YOUR_UNIT_ID'
      });
    }
  };

  return (
    <>
      <div ref={containerRef} />
      <button onClick={reloadAd}>광고 새로고침</button>
    </>
  );
}
```

<Note>
  Data Attributes로 생성한 지면에 `renderAd()`를 호출할 경우, data attributes에 존재하지 않는 항목이나 수정하고 싶은 항목만 전달하면 됩니다.
</Note>

### 광고 제거

`clear()`를 호출하여 컨테이너에서 광고를 제거할 수 있습니다.

```tsx theme={null}
const adrop = Adrop.instance();
adrop.clear(containerRef.current);
```

***

## 광고 크기

권장: 반응형 폭 + 유닛 등록 사이즈와 일치하는 `aspect-ratio`. 자리가 디바이스 폭에 맞춰 늘어나고 높이는 비율로 잠겨 CLS가 발생하지 않습니다.

```tsx theme={null}
<div
  data-adrop-unit="YOUR_UNIT_ID"
  data-adrop-backfill-mode="fixed"
  style={{ width: '100%', aspectRatio: '320 / 100' }}
/>
```

픽셀 단위 정렬이 필요한 경우 고정 픽셀 크기도 지원합니다.

```tsx theme={null}
<div
  data-adrop-unit="YOUR_UNIT_ID"
  style={{ width: 320, height: 100 }}
/>
```

| 구분    | 크기 설정                                                    | 정렬        |
| ----- | -------------------------------------------------------- | --------- |
| 직광고   | 반응형 지원, 높이 필수                                            | 가로/세로 가운데 |
| 백필 광고 | 컨테이너 폭에 맞춰 자동 매칭 (`responsive`) 또는 컨테이너 높이에 맞춤 (`fixed`) | 가로 가운데    |

<Note>
  백필 광고의 컨테이너 크기 작성 패턴과 렌더링 모드 선택 기준은 [백필 렌더링 모드](/ko/sdk/web/backfill) 가이드를 참고하세요.
</Note>

***

## 이벤트 처리

광고 상태 변화를 감지하여 적절한 UI 처리를 할 수 있습니다.

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function BannerAd({ unitId }: { unitId: string }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();

    // 광고 수신 성공
    const handleReceived = (unit: string, adData: any) => {
      console.log('광고 수신:', unit);
    };

    // 송출 가능한 직광고 없음
    const handleNoFill = (unit: string) => {
      console.warn('광고 없음:', unit);
    };

    // 요청 실패
    const handleFailed = (unit: string) => {
      console.error('광고 실패:', unit);
    };

    // 노출
    const handleImpression = (unit: string, adData: any) => {
      console.log('광고 노출:', unit);
    };

    // 클릭
    const handleClicked = (unit: string, adData: any) => {
      console.log('광고 클릭:', unit);
    };

    // 백필 광고 없음
    const handleBackfillNoFill = (unit: string) => {
      console.warn('백필 광고 없음:', unit);
    };

    // 동영상 재생 시작
    const handleVideoStart = (unit: string, adData: any) => {
      console.log('동영상 시작:', unit);
    };

    // 동영상 재생 완료
    const handleVideoEnd = (unit: string, adData: any) => {
      console.log('동영상 완료:', unit);
    };

    // 특정 유닛만 필터링
    const filter = { unit: unitId };

    adrop.on(Adrop.Events.AD_RECEIVED, handleReceived, filter);
    adrop.on(Adrop.Events.AD_NO_FILL, handleNoFill, filter);
    adrop.on(Adrop.Events.AD_FAILED, handleFailed, filter);
    adrop.on(Adrop.Events.AD_IMPRESSION, handleImpression, filter);
    adrop.on(Adrop.Events.AD_CLICKED, handleClicked, filter);
    adrop.on(Adrop.Events.AD_BACKFILL_NO_FILL, handleBackfillNoFill, filter);
    adrop.on(Adrop.Events.AD_VIDEO_START, handleVideoStart, filter);
    adrop.on(Adrop.Events.AD_VIDEO_END, handleVideoEnd, filter);

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, { unit: unitId });
    }

    return () => {
      adrop.off(Adrop.Events.AD_RECEIVED, handleReceived);
      adrop.off(Adrop.Events.AD_NO_FILL, handleNoFill);
      adrop.off(Adrop.Events.AD_FAILED, handleFailed);
      adrop.off(Adrop.Events.AD_IMPRESSION, handleImpression);
      adrop.off(Adrop.Events.AD_CLICKED, handleClicked);
      adrop.off(Adrop.Events.AD_BACKFILL_NO_FILL, handleBackfillNoFill);
      adrop.off(Adrop.Events.AD_VIDEO_START, handleVideoStart);
      adrop.off(Adrop.Events.AD_VIDEO_END, handleVideoEnd);
    };
  }, [unitId]);

  return <div ref={containerRef} />;
}
```

### 지원 이벤트

| 이벤트    | 상수                    | 설명            |
| ------ | --------------------- | ------------- |
| 광고 수신  | `AD_RECEIVED`         | 광고 요청 성공      |
| 광고 없음  | `AD_NO_FILL`          | 송출 가능한 직광고 없음 |
| 요청 실패  | `AD_FAILED`           | 광고 요청 실패      |
| 노출     | `AD_IMPRESSION`       | 광고 노출 기록됨     |
| 클릭     | `AD_CLICKED`          | 사용자가 광고 클릭    |
| 백필 없음  | `AD_BACKFILL_NO_FILL` | 백필 광고 없음      |
| 동영상 시작 | `AD_VIDEO_START`      | 동영상 광고 재생 시작  |
| 동영상 완료 | `AD_VIDEO_END`        | 동영상 광고 재생 완료  |

<Note>
  컴포넌트 언마운트 시 반드시 `off()`로 이벤트 리스너를 해제하세요.
</Note>

### AdData

이벤트 핸들러에서 전달받는 광고 데이터입니다. 자세한 필드 정보는 [레퍼런스 > AdData](/ko/sdk/web/reference#addata)를 참고하세요.

***

## 커스텀 클릭 처리

`useCustomClick`이 활성화되면 SDK가 자동으로 목적지 URL로 이동하지 않습니다. 대신 클릭 이벤트를 처리하여 사용자 정의 동작을 구현할 수 있습니다.

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function CustomClickBannerAd({ unitId }: { unitId: string }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();
    const filter = { unit: unitId };

    // 커스텀 동작으로 광고 클릭 처리
    const handleClicked = (unit: string, adData: any) => {
      console.log('광고 클릭:', unit);
      console.log('목적지 URL:', adData.destinationURL);
      
      // 커스텀 클릭 동작 구현
      // 예시: 확인 대화상자 표시, 분석 추적 등
      const userConfirmed = confirm('광고주 페이지를 방문하시겠습니까?');
      if (userConfirmed && adData.destinationURL) {
        window.open(adData.destinationURL, '_blank');
      }
    };

    adrop.on(Adrop.Events.AD_CLICKED, handleClicked, filter);

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, {
        unit: unitId,
        useCustomClick: true
      });
    }

    return () => {
      adrop.off(Adrop.Events.AD_CLICKED, handleClicked);
    };
  }, [unitId]);

  return <div ref={containerRef} />;
}
```

<Note>
  커스텀 클릭 처리가 활성화되면 사용자가 광고를 클릭할 때 어떤 일이 일어날지 완전히 제어할 수 있습니다. `destinationURL`은 `adClicked` 이벤트 데이터에 포함되어 제공됩니다.
</Note>

***

## 전체 예제

### BannerAd.tsx

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

interface BannerAdProps {
  unitId: string;
  contextId?: string;
  theme?: 'light' | 'dark';
  onReceived?: (adData: any) => void;
  onFailed?: () => void;
  onClicked?: () => void;
}

function BannerAd({
  unitId,
  contextId,
  theme = 'light',
  onReceived,
  onFailed,
  onClicked
}: BannerAdProps) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();
    const filter = { unit: unitId };

    const handleReceived = (_: string, adData: any) => onReceived?.(adData);
    const handleFailed = () => onFailed?.();
    const handleClicked = () => onClicked?.();

    adrop.on(Adrop.Events.AD_RECEIVED, handleReceived, filter);
    adrop.on(Adrop.Events.AD_FAILED, handleFailed, filter);
    adrop.on(Adrop.Events.AD_CLICKED, handleClicked, filter);

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, {
        unit: unitId,
        contextId,
        theme
      });
    }

    return () => {
      adrop.off(Adrop.Events.AD_RECEIVED, handleReceived);
      adrop.off(Adrop.Events.AD_FAILED, handleFailed);
      adrop.off(Adrop.Events.AD_CLICKED, handleClicked);
    };
  }, [unitId, contextId, theme]);

  return <div ref={containerRef} className="banner-ad-container" />;
}

export default BannerAd;
```

### 사용 예시

```tsx theme={null}
import BannerAd from './components/BannerAd';

function App() {
  return (
    <div>
      <header>
        <BannerAd
          unitId="HEADER_BANNER_UNIT_ID"
          onClicked={() => console.log('헤더 배너 클릭')}
        />
      </header>

      <main>
        <h1>콘텐츠</h1>
      </main>

      <footer>
        <BannerAd
          unitId="FOOTER_BANNER_UNIT_ID"
          theme="dark"
        />
      </footer>
    </div>
  );
}
```

***

## 백필 광고

백필 광고가 활성화되면 직광고가 없을 때 자동으로 백필 광고를 요청합니다. `adData.format`으로 광고 타입을 구분할 수 있습니다.

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { Adrop } from '@adrop/ads-web-sdk';

function BannerAd({ unitId }: { unitId: string }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const adrop = Adrop.instance();
    const filter = { unit: unitId };

    // 광고 수신 (직광고 또는 백필)
    const handleReceived = (unit: string, adData: any) => {
      if (adData.format === 'backfill') {
        console.log('백필 광고가 로드되었습니다');
      } else {
        console.log('직광고가 로드되었습니다');
      }
    };

    // 직광고 없음 (백필 요청 시작)
    const handleNoFill = (unit: string) => {
      console.log('직광고 없음, 백필 광고 요청 중...');
    };

    // 백필 광고도 없음
    const handleBackfillNoFill = (unit: string) => {
      console.warn('백필 광고도 없습니다');
      // 광고 영역 숨기기 등 처리
    };

    adrop.on(Adrop.Events.AD_RECEIVED, handleReceived, filter);
    adrop.on(Adrop.Events.AD_NO_FILL, handleNoFill, filter);
    adrop.on(Adrop.Events.AD_BACKFILL_NO_FILL, handleBackfillNoFill, filter);

    if (containerRef.current) {
      adrop.renderAd(containerRef.current, { unit: unitId });
    }

    return () => {
      adrop.off(Adrop.Events.AD_RECEIVED, handleReceived);
      adrop.off(Adrop.Events.AD_NO_FILL, handleNoFill);
      adrop.off(Adrop.Events.AD_BACKFILL_NO_FILL, handleBackfillNoFill);
    };
  }, [unitId]);

  return <div ref={containerRef} />;
}
```

<Note>
  **개발 환경 테스트**: 앱에 연결된 광고 유닛 중 하나라도 **수익화 중**이면 localhost에서도 백필 광고 요청이 가능합니다. 승인되지 않은 도메인에서는 `AD_BACKFILL_NO_FILL` 이벤트가 발생합니다.
</Note>
