29. 內嵌框架 (iframe)

<iframe> 將另一份 HTML 文件嵌入目前網頁。

它建立巢狀瀏覽環境(nested browsing context):一份具有自身內容、導覽及可能包含程式碼的獨立文件。

常見用途包括:

  • 第三方影片播放器。
  • 地圖。
  • 外部小工具。
  • 文件預覽。
  • 隔離的示範內容。
<iframe
  src="course-preview.html"
  title="課程預覽"
  width="640"
  height="360"
></iframe>

iframe 不只是視覺容器。 它會載入獨立文件,因此需要考慮無障礙功能、安全、私隱及效能。

29.1 基本語法

iframe 需要開始標籤及結束標籤:

<iframe
  src="course-preview.html"
  title="課程預覽"
></iframe>

它不是空元素。

一般 HTML 中,不要使用自行關閉語法:

<!-- HTML 中的錯誤寫法:這不會關閉 iframe。 -->
<iframe src="course-preview.html" />

嵌入文件不會成為父頁面 DOM 中的一般子內容。它具有自己的文件結構。

29.2 重要屬性

屬性用途
src要嵌入的文件 URL
srcdoc行內嵌入文件的 HTML 原始碼
title為框架提供描述性識別
width / height顯示尺寸,以 CSS 像素為單位
loading要求立即或延遲載入
sandbox對嵌入文件施加限制
allow設定特定瀏覽器功能的權限
referrerpolicy控制取得框架時傳送的來源頁面資訊
name為瀏覽環境命名,供導覽目標使用
allowfullscreen允許全螢幕請求,但仍受適用政策限制

只使用嵌入內容實際需要的屬性。

frameborder、marginwidth 和 marginheight 等屬性已過時。呈現樣式應使用 CSS。

29.3 src 屬性

src 識別要載入的文件:

<iframe
  src="course-preview.html"
  title="HTML 課程預覽"
></iframe>

URL 可以是相對或絕對路徑。

遠端網頁不一定允許嵌入,可能因以下原因拒絕:

  • 安全回應標頭。
  • 身分驗證要求。
  • 瀏覽器私隱限制。
  • 你的網頁安全政策。
  • 其他伺服器或網絡情況。

對第三方服務,應使用其文件指定的嵌入 URL,而不是假設一般網頁 URL 可以使用。

例如,影片服務的觀看頁面與嵌入播放器 URL 可能不同。

29.4 為框架提供描述性標題

提供簡潔標題,識別框架用途:

<iframe
  src="map.html"
  title="顯示培訓中心位置的地圖"
></iframe>

避免沒有幫助的標題,例如:

  • 「Iframe」
  • 「嵌入內容」
  • 「網頁」

有意義的標題能幫助輔助技術使用者區分框架,並決定是否進入。

不過,iframe 的標題不會令內部內容自動符合無障礙需要。嵌入文件仍然需要適當標題、標籤、文字替代、鍵盤操作及其他無障礙功能。

iframe 的 title 屬性與嵌入文件的 <title> 元素用途不同。如果你能控制嵌入頁面,應同時提供兩者。

29.5 替代方式與後備內容

不要依靠 iframe 標籤之間的內容,在現代瀏覽器中提供可見後備內容:

<!-- 這不是可靠的可見後備內容。 -->
<iframe src="course-preview.html" title="課程預覽">
  <p>暫時無法使用預覽。</p>
</iframe>

應將有用替代方式放在 iframe 之外:

<p>
  <a href="course-preview.html">直接開啟課程預覽</a>。
</p>

<iframe
  src="course-preview.html"
  title="課程預覽"
></iframe>

地圖也應在一般 HTML 中提供地址及交通指引。

影片應提供相關字幕、文字稿或其他無障礙替代內容。

不要讓 iframe 成為取得重要資訊的唯一方式。

29.6 使用 srcdoc 提供行內文件

srcdoc 直接提供 HTML:

<iframe
  title="標題及段落範例"
  sandbox
  srcdoc="<!doctype html>
    <html lang="zh-Hant-HK">
      <head>
        <meta charset="utf-8">
        <title>HTML 範例</title>
      </head>
      <body>
        <h1>你好</h1>
        <p>這是一份獨立文件。</p>
      </body>
    </html>"
></iframe>

因為 HTML 放在屬性內,引號及其他相關字元必須適當轉義。

重要事項包括:

  • 瀏覽器支援且存在 srcdoc 時,它會優先於 src。
  • srcdoc 文件中的相對 URL,通常根據嵌入它的文件 URL 解析。
  • 沒有適當沙箱限制時,srcdoc 文件可以與父頁面共享來源。
  • 將不可信 HTML 指派給 srcdoc,可能造成注入漏洞。

不要將 srcdoc 視為安全顯示任意使用者內容的方式。應採取適當的內容清理及隔離措施。

29.7 sandbox 屬性

sandbox 限制嵌入文件:

<iframe
  src="static-preview.html"
  title="靜態課程預覽"
  sandbox
></iframe>

空的 sandbox 會套用整套沙箱限制。其中包括限制執行程式碼、提交表單、開啟彈出視窗及導覽頂層頁面,並將文件視為具有不透明來源(opaque origin)。

可以使用權限關鍵字,選擇性放寬限制:

關鍵字放寬的限制
allow-scripts執行程式碼
allow-forms提交表單
allow-same-origin保留資源原本的來源
allow-popups開啟輔助瀏覽環境
allow-downloads啟動下載
allow-top-navigation-by-user-activation使用者啟動操作後導覽頂層頁面

例如:

<iframe
  src="interactive-preview.html"
  title="互動課程預覽"
  sandbox="allow-scripts"
></iframe>

這允許執行程式碼,同時保留其他沙箱限制。

只授予嵌入內容實際需要的能力。 應測試結果,因為沙箱限制可能停用嵌入頁面預期使用的功能。

沙箱不會阻止所有網絡請求,也不會令任意內容變得無害。

29.8 需要特別注意的沙箱組合

對以下組合應特別小心:

sandbox="allow-scripts allow-same-origin"

allow-same-origin 不會令遠端網頁與你的頁面共享來源。它只是讓嵌入資源保留原本的來源,而不是取得不透明來源。

不過,如果嵌入文件本來就與父頁面同來源,同時允許執行程式碼及同來源存取,可能讓它存取嵌入元素、移除 sandbox 屬性,然後重新載入以解除限制。

因此:

  • 不要將這個組合用作不可信同來源內容的安全邊界。
  • 考慮從獨立來源提供不可信內容。
  • 檢查完整隔離設計,而不是只依靠一個屬性。

沙箱是有用工具,但不能取代審慎的信任判斷。

29.9 使用 allow 設定權限

allow 透過權限政策(Permissions Policy),控制特定瀏覽器功能的存取:

<iframe
  src="video-player.html"
  title="HTML 入門影片播放器"
  allow="fullscreen"
></iframe>

其他功能可以包括攝影機、麥克風及地理位置。

除非應用程式需要,否則不要授予這些權限。

重要區別包括:

  • allow 設定功能權限。
  • sandbox 限制文件能力。
  • 兩者不能互相取代。
  • 回應標頭及瀏覽器政策也可以限制權限。
  • 允許功能,不會繞過必要的使用者授權提示。

全螢幕設定也可能使用布林屬性 allowfullscreen:

<iframe
  src="video-player.html"
  title="HTML 入門影片播放器"
  allowfullscreen
></iframe>

應使用服務提供者建議的設定,再檢查每項要求的能力是否必要。

29.10 同來源限制

同來源政策通常會阻止父頁面讀取或修改跨來源 iframe 的文件。

來源由通訊協定、主機及連接埠的組合決定。

對允許存取的同來源框架,程式碼可能如下:

const frame = document.getElementById("preview");

frame.addEventListener("load", () => {
  const documentInsideFrame = frame.contentDocument;

  if (documentInsideFrame) {
    console.log(documentInsideFrame.title);
  }
});

跨來源或受到適當沙箱限制的框架,會限制這類文件存取。

合作文件需要跨來源通訊時,應使用 postMessage,並:

  • 可行情況下,明確指定預期的目標來源。
  • 檢查收到訊息的 origin。
  • 檢查其 source。
  • 驗證訊息結構及內容。

不要只因訊息透過 message 事件到達,就信任它。

29.11 私隱、來源頁面資訊與嵌入政策

第三方 iframe 可能在訪客操作之前就發起請求。

這些請求可能透露訪客的 IP 地址,並涉及 Cookie 或其他儲存機制,但仍受瀏覽器限制。

對非必要外部內容,應考慮由使用者啟動嵌入載入。

可以限制傳送的來源頁面資訊:

<iframe
  src="external-preview.html"
  title="外部課程預覽"
  referrerpolicy="no-referrer"
></iframe>

這本身不會令請求匿名,也不會阻止追蹤。

兩個內容安全政策指令處理不同的嵌入問題:

指令控制
frame-src你的頁面可以載入哪些框架資源
frame-ancestors哪些頁面可以嵌入你的文件

例如,回應標頭可以禁止其他頁面以框架嵌入文件:

Content-Security-Policy: frame-ancestors 'none'

frame-ancestors 必須透過 HTTP 回應標頭提供,不支援放在 CSP <meta> 元素內。

較舊的 X-Frame-Options 回應標頭也可以限制嵌入。

不要嘗試繞過其他網站的嵌入限制。

29.12 響應式尺寸與延遲載入

使用 CSS 控制框架版面:

.video-frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
}

文件或應用程式預覽可能更適合使用明確高度:

.document-frame {
  display: block;
  width: 100%;
  height: 30rem;
  border: 1px solid #767676;
}

父頁面的 CSS 不會自動套用至 iframe 內的文件。

iframe 也不會自動調整至內部文件的內容高度。這類行為可能需要雙方協調的程式碼。

對螢幕範圍之外的內容,可以要求延遲載入:

<iframe
  src="course-preview.html"
  title="課程預覽"
  loading="lazy"
  width="640"
  height="360"
></iframe>

loading="lazy" 是由瀏覽器管理的載入提示,不保證完全沒有初始請求。應預留足夠版面空間,減少內容載入時的位移。

29.13 完整範例:沙箱 HTML 預覽

以下獨立網頁在具有沙箱限制的 iframe 內,顯示簡短行內文件。

它不使用外部資源,也不授予任何沙箱例外權限。iframe 用來示範文件分離,而不是互動應用程式。

id: hong-kong-html-iframes
name: 內嵌框架 — 沙箱 HTML 預覽
type: html
content: |-
  <!doctype html>
  <html lang="zh-Hant-HK">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>內嵌框架 | 沙箱 HTML 預覽</title>
    <style>
      *,
      *::before,
      *::after {
        box-sizing: border-box;
      }

      body {
        margin: 0;
        color: #222;
        background: #f5f7fa;
        font-family: system-ui, sans-serif;
        line-height: 1.8;
      }

      main {
        max-width: 48rem;
        margin-inline: auto;
        padding: 2rem 1rem;
      }

      h1,
      h2,
      h3 {
        color: #005ea8;
        line-height: 1.4;
      }

      section {
        margin-block: 1.5rem;
        padding: 1.25rem;
        border: 1px solid #767676;
        border-radius: 0.5rem;
        background: white;
      }

      section > h2 {
        margin-block-start: 0;
      }

      .note {
        padding: 1rem;
        border-inline-start: 4px solid #005ea8;
        background: #eaf2f8;
      }

      .preview-frame {
        display: block;
        width: 100%;
        height: 22rem;
        border: 1px solid #767676;
        background: white;
      }

      a {
        color: #005ea8;
        text-decoration: underline;
        text-underline-offset: 0.2em;
      }

      :focus-visible {
        outline: 3px solid #005ea8;
        outline-offset: 3px;
      }

      [id] {
        scroll-margin-block-start: 1rem;
      }
    </style>
  </head>
  <body>
    <main>
      <h1>沙箱 HTML 預覽</h1>

      <p class="note">
        下方預覽是一份獨立 HTML 文件。
        空的 sandbox 屬性會套用限制,
        不授予任何例外權限。
      </p>

      <p>
        頁面亦提供相同內容的文字版本。
      </p>

      <section aria-labelledby="preview-heading">
        <h2 id="preview-heading">嵌入預覽</h2>

        <p>
          你也可以
          <a href="#text-version">閱讀下方的文字版本</a>。
        </p>

        <iframe
          class="preview-frame"
          title="HTML 入門課程預覽"
          width="640"
          height="352"
          sandbox
          srcdoc="<!doctype html>
            <html lang="zh-Hant-HK">
              <head>
                <meta charset="utf-8">
                <meta name="viewport"
                  content="width=device-width, initial-scale=1">
                <title>HTML 入門課程</title>
                <style>
                  body {
                    margin: 0;
                    padding: 1.25rem;
                    color: #222;
                    background: #fff;
                    font-family: system-ui, sans-serif;
                    line-height: 1.8;
                  }
                  h1 {
                    color: #005ea8;
                    font-size: 1.5rem;
                    line-height: 1.4;
                  }
                </style>
              </head>
              <body>
                <h1>HTML 入門課程</h1>
                <p>學習建立清晰、符合無障礙需要的網頁。</p>
                <ul>
                  <li>使用有意義的標題組織內容。</li>
                  <li>為表單控制元素提供標籤。</li>
                  <li>為有意義的圖片加入文字替代。</li>
                </ul>
              </body>
            </html>"
        ></iframe>
      </section>

      <section aria-labelledby="text-version">
        <h2 id="text-version">預覽的文字版本</h2>
        <h3>HTML 入門課程</h3>
        <p>學習建立清晰、符合無障礙需要的網頁。</p>
        <ul>
          <li>使用有意義的標題組織內容。</li>
          <li>為表單控制元素提供標籤。</li>
          <li>為有意義的圖片加入文字替代。</li>
        </ul>
      </section>
    </main>
  </body>
  </html>

在這個範例中:

  • srcdoc 提供完整嵌入文件。
  • iframe 具有描述性的 title。
  • 嵌入文件具有自己的語言及文件標題。
  • 空的 sandbox 套用限制,不授予例外權限。
  • 內部文件定義自己的樣式。
  • CSS 讓框架符合父頁面的可用寬度。
  • 框架之外提供相同內容。
  • 不需要程式碼或第三方請求。

預覽高度是固定的。如果內部內容增加或文字放大,框架可能需要捲動;外部文字版本仍然可用。

29.14 常見錯誤

錯誤較好的做法
在 HTML 中使用 iframe 自行關閉語法明確寫出結束標籤
省略描述性標題識別框架用途
以為標題能修復無障礙內容問題讓嵌入文件本身也符合無障礙需要
將重要後備文字放在 iframe 標籤之間將替代內容放在 iframe 之外
未經檢查就嵌入一般服務 URL使用文件指定的嵌入 URL
授予所有沙箱例外權限只允許必要能力
將程式碼加同來源存取視為可靠隔離檢查來源及沙箱設計
將 allow 和 sandbox 視為相同功能分別設定權限及限制
將不可信 HTML 注入 srcdoc適當清理及隔離內容
以為父頁面 CSS 會套用至框架內容分別設定嵌入文件樣式
未經驗證就信任跨來源訊息檢查來源、發送視窗及訊息資料
將 iframe 的 load 事件當成成功證明驗證實際內容及可用性
立即載入非必要第三方嵌入內容考慮延遲或由使用者啟動載入
使用過時呈現屬性使用 CSS

本章總結: <iframe> 嵌入的是獨立文件,而不是一般子內容。應提供描述性標題、無障礙替代內容及適當尺寸。嵌入外部或不可信內容之前,應檢查沙箱限制、功能權限、來源邊界、私隱及載入行為。

Scroll to Top