---
# System prepended metadata

title: Nuxt3 嵌入動態 YouTube
tags: [已重新整理]

---

# Nuxt3 嵌入動態 YouTube

如何在 Nuxt3 中動態嵌入 YouTube 影片,支援多種 YouTube 網址格式。

---

## 功能特色

✅ 支援多種 YouTube 網址格式:
- 標準網址: `https://www.youtube.com/watch?v=VIDEO_ID`
- 短網址: `https://youtu.be/VIDEO_ID`
- 嵌入網址: `https://www.youtube.com/embed/VIDEO_ID`
- 直播網址: `https://www.youtube.com/live/VIDEO_ID`

✅ 自動驗證網址格式  
✅ 響應式設計,支援 RWD  
✅ 錯誤處理機制

---

## 檔案結構

```
project/
├── pages/
│   └── video.vue                      # 頁面組件
└── composables/
    └── useYoutubeVerification.js      # YouTube 驗證邏輯
```

---

## 實作步驟

### 1. Composable - YouTube 驗證與轉換

建立 `composables/useYoutubeVerification.js`:

```javascript
export const useYoutubeVerification = () => {
  /**
   * 驗證 YouTube 網址並提取影片 ID
   * @param {string} data - YouTube 網址
   * @returns {string|null} 影片 ID 或 null
   */
  const youtubeVerification = (data) => {
    if (!data) return null
    
    // YouTube 網址格式正則表達式
    const youtubeRegex = /(?:youtu\.be\/|youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=|live\/))([^"&?\/\s]{11})/
    
    // 使用 match() 提取影片 ID
    // match() 返回陣列或 null
    const match = data.match(youtubeRegex)
    return match ? match[1] : null
  }

  /**
   * 將 YouTube 網址轉換為嵌入格式
   * @param {string} videoUrl - YouTube 網址
   * @returns {string|null} 嵌入網址或 null
   */
  const setVideo = (videoUrl) => {
    const videoId = youtubeVerification(videoUrl)
    
    if (!videoId) return null
    
    // 返回標準嵌入格式
    return `https://www.youtube.com/embed/${videoId}`
  }

  return {
    youtubeVerification,
    setVideo
  }
}
```

---

### 2. 頁面組件

建立 `pages/video.vue`:

#### Template

```vue
<template>
  <div class="video-container">
    <!-- 影片存在且格式正確 -->
    <template v-if="youtubeVerification(videoUrl)">
      <div class="iframe-container">
        <iframe 
          class="responsive-iframe" 
          :src="setVideo(videoUrl)" 
          title="YouTube video player" 
          frameborder="0" 
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" 
          allowfullscreen
        ></iframe>
      </div>
    </template>

    <!-- 影片不存在或格式錯誤 -->
    <template v-else>
      <div class="error-message">
        <p>影片網址格式錯誤或影片不存在</p>
        <p class="error-hint">請確認 YouTube 網址是否正確</p>
      </div>
    </template>
  </div>
</template>
```

#### Script Setup

```vue
<script setup>
// 引入 YouTube 驗證邏輯
const { youtubeVerification, setVideo } = useYoutubeVerification()

// 方式一: 使用 useFetch (推薦 - 適合外部 API)
const { data: videoUrl } = await useFetch('/api/video')

// 方式二: 使用 useAsyncData + $fetch (推薦 - 更靈活)
// const { data: videoUrl } = await useAsyncData('videoUrl', () => $fetch('/api/video'))

// 方式三: 直接設定影片網址 (測試用)
// const videoUrl = ref('https://www.youtube.com/watch?v=dQw4w9WgXcQ')

// 方式四: 從外部 API 取得
// const { data: videoUrl } = await useFetch('https://your-api.com/video-url')
</script>
```

#### Style (SCSS)

```vue
<style lang="scss" scoped>
/* ============================
   方法一: 使用 padding-top 維持比例
   ============================ */
/*
.iframe-container {
  position: relative;
  overflow: hidden;
  width: 100%;
  padding-top: 56.25%; // 16:9 比例 (9/16 * 100%)
  
  .responsive-iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: none;
  }
}
*/

/* ============================
   方法二: 使用 aspect-ratio (推薦)
   ============================ */
.iframe-container {
  width: 100%;
  aspect-ratio: 16 / 9; // 16:9 比例
  overflow: hidden;
  border-radius: 8px; // 可選: 圓角
  
  .responsive-iframe {
    width: 100%;
    height: 100%;
    border: none;
  }
}

/* 錯誤訊息樣式 */
.error-message {
  padding: 2rem;
  text-align: center;
  background: #fff3cd;
  border: 1px solid #ffc107;
  border-radius: 8px;
  
  p {
    margin: 0.5rem 0;
    color: #856404;
    
    &.error-hint {
      font-size: 0.875rem;
      color: #856404;
      opacity: 0.8;
    }
  }
}

/* 容器樣式 */
.video-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 1rem;
}

/* 響應式設計 */
@media (max-width: 768px) {
  .video-container {
    padding: 0.5rem;
  }
}
</style>
```