> For the complete documentation index, see [llms.txt](https://docs.exoclick.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.exoclick.com/tutorials/ja/chtoriaru/paburisshkechtoriaru/fpi-spa-with-exoclick.md).

# シングルページアプリケーションにおけるフルページインタースティシャル広告

このガイドでは、実装方法を示します **フルページインタースティシャル（FPI）** 広告を、直接 API 呼び出しを使ってシングルページアプリケーション（SPA）に実装します。React、Vue、Angular、その他のフレームワークで動作します。

<figure><img src="/files/b6aa51b5b1e4d7638e98b320c94f35882bded123" alt=""><figcaption></figcaption></figure>

## ステップごとの実装

{% stepper %}
{% step %}

### コンポーネントの state を設定

まず、広告システムとナビゲーションを追跡する state 変数を作成します：

```typescript
'use client'

import { useEffect, useState } from 'react'

export default function HomePage() {
  const [adLoaded, setAdLoaded] = useState(false)
  const [currentPage, setCurrentPage] = useState('home')

  useEffect(() => {
    setAdLoaded(true)
    console.log('✅ FPI API メソッドを初期化しました')
  }, [])
}
```

{% endstep %}

{% step %}

### API リクエスト関数を作成

この関数は FPI 広告サーバーに直接 HTTP リクエストを送信します：

```typescript
const triggerFPIAd = async () => {
  console.log('🎯 FPI サービスへ直接 API 呼び出しを実行しています...')

  try {
    // ユーザーコンテキストを使ってリクエストを準備
    const requestBody = {
      user: {
        ua: navigator.userAgent,
        language: navigator.language || 'en-US',
        referer: window.location.href,
        consumer: 'ad-provider',
        gdpr: { gdpr: 0 },
        screen_resolution: `${screen.width}x${screen.height}`,
        window_orientation: screen.width > screen.height ? 'landscape' : 'portrait',
        cookies: [],
        scr_info: btoa('async||3')
      },
      zones: [{
        custom_targeting: {},
        id: 1234567, // あなたの FPI ゾーン ID（モバイル端末ではモバイル FPI ゾーン ID を使用してください）
        extra_params: {
          first_request: true,
          zone_type: 35 // インタースティシャル形式
        }
      }]
    }

    // API 呼び出しを実行
    const response = await fetch('https://s.pemsrv.com/v1/api.php', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(requestBody)
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    const data = await response.json()
    console.log('📥 API の完全なレスポンス:', data)

    // レスポンスを処理
    if (data.zones && data.zones.length > 0) {
      const zone = data.zones[0]
     
      if (zone.data) {
        console.log('🎨 広告を表示しています...')
        await displayInterstitialAd(zone.data)
      } else {
        console.log('❌ レスポンスに広告データがありません')
      }
    }

  } catch (error) {
    console.error('❌ FPI API エラー:', error)
  }
}
```

{% endstep %}

{% step %}

### 表示関数を作成

この関数は複数の広告形式を処理します

```typescript
const displayInterstitialAd = async (adData: any) => {
  console.log('🎨 インタースティシャル広告の表示を開始します...')

  try {
    // 既存のオーバーレイを削除
    const existingOverlay = document.getElementById('fpi-interstitial-overlay')
    if (existingOverlay) {
      document.body.removeChild(existingOverlay)
    }

    // 全画面オーバーレイを作成
    const overlay = document.createElement('div')
    overlay.id = 'fpi-interstitial-overlay'
    overlay.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      background-color: rgba(0, 0, 0, 0.9);
      z-index: 999999;
      display: flex;
      align-items: center;
      justify-content: center;
    `

    // 広告コンテナを作成
    const adContainer = document.createElement('div')
    adContainer.style.cssText = `
      position: relative;
      width: 100vw;
      height: 100vh;
      background: white;
      overflow: hidden;
    `

    // 閉じるボタンを作成
    const closeButton = document.createElement('button')
    closeButton.innerHTML = '✕'
    closeButton.style.cssText = `
      position: absolute;
      top: 10px;
      right: 10px;
      width: 40px;
      height: 40px;
      border: none;
      background: rgba(0, 0, 0, 0.7);
      color: white;
      font-size: 20px;
      border-radius: 50%;
      cursor: pointer;
      z-index: 1000000;
    `

    closeButton.onclick = () => {
      document.body.removeChild(overlay)
      console.log('🚪 インタースティシャル広告を閉じました')
    }

    // 広告形式を判定してコンテンツを作成
    let adContent = ''

    // 形式 1: iframe 広告
    if (adData.html && adData.html.includes('iframe.php')) {
      adContent = `
        <iframe
          src="${adData.html}"
          width="100%"
          height="100%"
          frameborder="0"
          allowfullscreen
          sandbox="allow-scripts allow-forms allow-popups"
          style="width: 100vw; height: 100vh; border: none;"
        ></iframe>
      `
      console.log('🖼️ iframe 広告を作成しました')
    }
```

<details>

<summary>クリックして追加の広告形式を展開</summary>

```typescript
    if (adData.html && adData.html.includes('iframe.php')) {
      adContent = `
        <iframe
          src="${adData.html}"
          width="100%"
          height="100%"
          frameborder="0"
          allowfullscreen
          sandbox="allow-scripts allow-forms allow-popups"
          style="width: 100vw; height: 100vh; border: none;"
        ></iframe>
      `
      console.log('🖼️ iframe 広告を作成しました')
    }
   
    // 形式 2: 動画広告
    else if (adData.video) {
      const hasClickUrl = adData.url && adData.url.trim() !== ''

      if (hasClickUrl) {
        // クリック遷移付き動画
        adContent = `
          <div style="position: relative; width: 100vw; height: 100vh; cursor: pointer;"
               onclick="window.open('${adData.url}', '_blank')">
            <video
              width="100%"
              height="100%"
              autoplay
              muted
              loop
              style="object-fit: cover; width: 100vw; height: 100vh;"
            >
              <source src="${adData.video}" type="video/mp4">
            </video>
            <div style="
              position: absolute;
              bottom: 20px;
              left: 50%;
              transform: translateX(-50%);
              background: rgba(0, 0, 0, 0.7);
              color: white;
              padding: 8px 16px;
              border-radius: 20px;
              font-size: 14px;
            ">
              広告主のサイトへ行くにはクリック
            </div>
          </div>
        `
        console.log('🎬 クリック可能な動画バナー広告を作成しました')
      } else {
        // 通常の動画
        adContent = `
          <video
            width="100%"
            height="100%"
            autoplay
            muted
            controls
            style="object-fit: cover; width: 100vw; height: 100vh;"
          >
            <source src="${adData.video}" type="video/mp4">
          </video>
        `
        console.log('🎬 動画広告を作成しました')
      }
    }
   
    // 形式 3: クリック遷移広告
    else if (adData.url) {
      adContent = `
        <div style="
          width: 100vw;
          height: 100vh;
          display: flex;
          align-items: center;
          justify-content: center;
          flex-direction: column;
          background: linear-gradient(45deg, #0072ba, #28a745);
          padding: 20px;
        ">
          <h2 style="color: white; margin-bottom: 20px; font-size: 48px;">
            🎯 インタースティシャル広告
          </h2>
          <p style="color: white; margin-bottom: 30px; font-size: 24px;">
            広告主のサイトへ行くにはクリック
          </p>
          <a href="${adData.url}"
             target="_blank"
             style="
               padding: 15px 30px;
               background: white;
               color: #0072ba;
               text-decoration: none;
               border-radius: 25px;
               font-weight: bold;
               font-size: 18px;
             ">
            今すぐ訪問
          </a>
        </div>
      `
      console.log('🔗 クリック遷移広告を作成しました')
    }
   
    // 形式 4: ネイティブ広告
    else if (adData.ad_items && adData.ad_items.length > 0) {
      const adItemsHtml = adData.ad_items.map((item: any) => `
        <div style="
          background: white;
          border-radius: 8px;
          overflow: hidden;
          box-shadow: 0 2px 8px rgba(0,0,0,0.1);
          cursor: pointer;
        " onclick="window.open('${item.url}', '_blank')">
          <div style="height: 200px; overflow: hidden;">
            <img src="${item.optimum_image || item.image}"
                 style="width: 100%; height: 100%; object-fit: cover;"
                 alt="${item.title}">
          </div>
          <div style="padding: 15px;">
            <h3 style="margin: 0 0 8px 0; font-size: 18px;">${item.title}</h3>
            ${item.description ? `<p style="font-size: 14px; color: #666;">${item.description}</p>` : ''}
            <div style="font-size: 12px; color: #999;">${item.brand || 'スポンサー'}</div>
          </div>
        </div>
      `).join('')

      adContent = `
        <div style="
          width: 100vw;
          height: 100vh;
          background: #f5f5f5;
          overflow-y: auto;
          padding: 20px;
        ">
          <h2 style="text-align: center; margin-bottom: 20px;">スポンサーコンテンツ</h2>
          <div style="
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 15px;
          ">
            ${adItemsHtml}
          </div>
        </div>
      `
      console.log('🎯 ネイティブ広告グリッドを作成しました')
    }

    // コンテナにコンテンツを追加
    adContainer.innerHTML = adContent
    adContainer.appendChild(closeButton)
    overlay.appendChild(adContainer)
    document.body.appendChild(overlay)

    // インプレッションを追跡
    if (adData.impression) {
      fetch(adData.impression, { method: 'GET', mode: 'no-cors' })
        .then(() => console.log('📊 インプレッションを記録しました'))
        .catch(e => console.log('⚠️ インプレッションの追跡に失敗しました:', e))
    }

  } catch (error) {
    console.error('❌ 広告表示エラー:', error)
  }
}
```

</details>
{% endstep %}

{% step %}

### トリガーボタンを追加

```tsx
return (
  <div>
    <h1>マイページ</h1>
    <button onClick={triggerFPIAd}>
      FPI広告を読み込む
    </button>
  </div>
)
```

{% endstep %}
{% endstepper %}

## 完全なコード例

実際の動作を見てみませんか？ 上記のすべてのステップをまとめた、動作する実装のインタラクティブデモをご覧ください。

**ライブデモ：** [コードとデモサイトを見る](https://codesandbox.io/p/devbox/exo-spa-fpi-58zynm)

<figure><img src="/files/488f1a1715aba0af3d2c1d0c5e62ca1206ca71a0" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.exoclick.com/tutorials/ja/chtoriaru/paburisshkechtoriaru/fpi-spa-with-exoclick.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
