- HTML
HTML iframeの使い方|レスポンシブ・sandbox・表示されない原因
iframe(インラインフレーム)とは、HTMLページの中に別のページを枠として埋め込む要素で、src 属性に埋め込みたいページのURLを書いて使います。 動画・地図・フォーム・別サイトのウィジェットなど、「ほかのページをそのまま自分のページに表示したい」ときの標準的な方法です。
この記事では、基本の書き方に加えて、「埋め込んだのに表示されない」ときの原因を、ブラウザのコンソールに出るエラーメッセージ別に切り分ける早見表を用意しました。curl -I でレスポンスヘッダーを確認する手順、aspect-ratio を使ったレスポンシブ対応、sandbox の全トークンと allow 属性の一覧、postMessage による高さの自動調整、nginx / Apache で自サイトの埋め込み可否を設定する例まで、コピペ用のコードで示します。エラーメッセージと数値は Chromium 141(Playwright 1.56.0)と nginx 1.24.0、curl 8.5.0 で実際に確認したものです。メッセージ内のURLは parent.example(埋め込む側)と child.example(埋め込まれる側)に置き換えています。Firefox と Safari での表示は未実行です。
iframeの早見表
属性の早見表
| 属性 | 役割 | 書き方の例 |
|---|---|---|
src |
埋め込むページのURL | src="https://example.com/embed" |
title |
枠の内容の説明(読み上げソフト向け) | title="アクセスマップ" |
width / height |
幅と高さ(省略時は 300×150) | width="560" height="315" |
loading |
読み込みのタイミング | loading="lazy" |
sandbox |
枠の中でできることを制限する | sandbox="allow-scripts allow-forms" |
allow |
カメラや全画面などの機能を許可する | allow="fullscreen" |
referrerpolicy |
参照元情報の送り方 | referrerpolicy="no-referrer" |
srcdoc |
URLの代わりにHTMLを直接書く | srcdoc="<p>こんにちは</p>" |
name |
リンクやフォームの表示先として使う名前 | name="preview" |
表示されないときの早見表
| コンソールのメッセージ(先頭部分) | 原因 | 直す場所 |
|---|---|---|
Refused to display ... because it set 'X-Frame-Options' to 'deny' |
埋め込まれる側が埋め込みを全面的に断っている | 埋め込まれる側のサーバー設定 |
Refused to display ... to 'sameorigin' |
同じオリジンからの埋め込みだけ許可されている | 埋め込まれる側のサーバー設定 |
Refused to frame ... "frame-ancestors ..." |
CSP で埋め込み元が許可されていない | 埋め込まれる側のCSP |
Mixed Content: ... requested an insecure frame |
https のページに http のページを埋め込んだ | src を https にする |
Blocked script execution ... 'allow-scripts' permission is not set |
sandbox でスクリプトが止まっている | 埋め込む側の sandbox |
| メッセージなしでログインが外れる | 枠の中にCookieが送られていない | 埋め込まれる側のCookie属性 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「HTML iframeの使い方|レスポンシブ・sandbox・表示されない原因」
iframeの基本の書き方
<iframe
src="https://example.com/embed"
title="埋め込みコンテンツの説明"
width="560"
height="315"
loading="lazy"
style="border: 0;"
></iframe>
ポイントは3つです。
| ポイント | 理由 |
|---|---|
title を必ず書く |
読み上げソフトが「何の枠か」を伝えるために使う |
width と height を書く |
読み込み前から場所が確保され、ページのがたつきを防げる |
枠線は CSS の border: 0 で消す |
何も指定しないと 2px の枠線が付く |
Chromium 141 で既定の状態を測ったところ、サイズ指定なしの iframe は 300×150px、枠線は 2px(inset)、display は inline でした。枠線を含めた外寸は 304×154px になります。
終了タグ </iframe> は省略できません。また、iframe は画像と同じインライン要素なので、下に数pxのすき間ができることがあります。気になるときは display: block を指定します。
HTMLを直接書いて埋め込む srcdoc
<iframe
title="プレビュー"
sandbox
srcdoc="<p>ここに <strong>HTML</strong> を直接書けます</p>"
width="300" height="100"
></iframe>
srcdoc は、ユーザーが入力したHTMLのプレビューなどに使います。sandbox と組み合わせると、中のスクリプトを動かさずに表示だけできます。
レスポンシブ対応:画面幅に合わせて縮める
動画のように縦横比を保ちたい埋め込みは、CSS の aspect-ratio を使うのが現在の定番です。
aspect-ratio を使う方法
<iframe
class="embed-16x9"
src="https://example.com/embed"
title="紹介動画"
width="560" height="315"
loading="lazy"
allow="fullscreen"
></iframe>
.embed-16x9 {
display: block;
width: 100%;
height: auto; /* height 属性の値を打ち消す */
aspect-ratio: 16 / 9; /* 幅に対して高さを自動計算 */
border: 0;
}
padding-top を使う方法(従来の書き方)
<div class="embed-wrap">
<iframe src="https://example.com/embed" title="紹介動画"></iframe>
</div>
.embed-wrap {
position: relative;
width: 100%;
padding-top: 56.25%; /* 9 ÷ 16 = 0.5625 */
}
.embed-wrap iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 0;
}
実測での比較
画面幅を 1200px・800px・375px に変えて、iframe の大きさを測りました。
| 書き方 | 幅 1200px | 幅 800px | 幅 375px | 縦横比 |
|---|---|---|---|---|
aspect-ratio: 16 / 9 + width: 100% + height: auto |
1200×675 | 800×450 | 375×211 | 保たれる |
| padding-top: 56.25% の入れ物 | 1200×675 | 800×450 | 375×211 | 保たれる |
width: 100% だけ(height 属性 315 のまま) |
1200×315 | 800×315 | 375×315 | 崩れる |
max-width: 100% だけ |
560×315 | 560×315 | 375×315 | 狭い画面で崩れる |
max-width: 560px + aspect-ratio: 560 / 315 |
560×315 | 560×315 | 375×211 | 保たれる |
aspect-ratio と padding-top の結果は同じでした。違いは書く量です。
| 比べる点 | aspect-ratio | padding-top |
|---|---|---|
| 必要なHTML | iframe だけ | 入れ物の div が必要 |
| 比率の指定 | 16 / 9 と直感的に書ける |
56.25% と計算が必要 |
| 最大幅を付けたい | max-width を足すだけ |
入れ物に max-width を付ける |
| 向いている場面 | これから書くページ全般 | 既存のCSSがこの方式で組まれている場合 |
元の大きさ(560×315)より大きくしたくないときは、5行目の書き方が使えます。
.embed-fixed-max {
display: block;
width: 100%;
max-width: 560px;
height: auto;
aspect-ratio: 560 / 315;
border: 0;
}
注意点は height: auto を忘れないことです。HTML の height="315" が残っていると高さが固定され、aspect-ratio が効きません(表の3行目の状態になります)。
遅延読み込み:loading="lazy"
画面の外にある iframe の読み込みを、近づくまで待たせる属性です。
<iframe src="https://example.com/embed" title="地図"
width="600" height="450" loading="lazy"></iframe>
高さ 6,000px の余白の下に、loading なしの iframe と loading="lazy" の iframe を置いて、サーバーに届いたリクエストを調べました(画面は 800×600px)。
| タイミング | loading なし(上) | loading なし(6,000px 下) | loading="lazy"(6,000px 下) |
|---|---|---|---|
| ページを開いた直後 | 読み込まれた | 読み込まれた | 読み込まれない |
| 2,000px スクロール後 | ― | ― | 読み込まれた |
loading="lazy" を付けた iframe だけが、スクロールで近づくまで読み込まれませんでした。 画面に入る少し手前(今回は 2,000px スクロールした時点)で読み込みが始まっています。どのくらい手前で始まるかはブラウザが決めます。
| 使いどころ | 指定 |
|---|---|
| ページ下部の地図・動画・コメント欄 | loading="lazy" |
| ページを開いてすぐ見える位置の埋め込み | 指定なし(すぐ読み込む) |
MDN には、遅延読み込みは JavaScript が有効なときだけ働くと記載されています。
sandbox:枠の中でできることを制限する
sandbox は、埋め込んだページの動作を制限する属性です。値なしで書くとすべての制限がかかり、トークン(許可の名前)を書いたものだけが解除されます。
<!-- すべて制限 -->
<iframe sandbox src="https://child.example/page" title="制限つき"></iframe>
<!-- スクリプトとフォーム送信だけ許可 -->
<iframe sandbox="allow-scripts allow-forms"
src="https://child.example/page" title="一部許可"></iframe>
sandbox のトークン一覧
MDN の iframe 要素のページに載っているトークンです。
| トークン | 許可すること |
|---|---|
allow-scripts |
スクリプトの実行 |
allow-forms |
フォームの送信 |
allow-same-origin |
本来のオリジンとして扱う(Cookie や localStorage を使えるようにする) |
allow-popups |
window.open() や target="_blank" で新しいウィンドウを開く |
allow-popups-to-escape-sandbox |
開いた新しいウィンドウに sandbox の制限を引き継がせない |
allow-modals |
alert() confirm() prompt() print() などのダイアログ |
allow-downloads |
ファイルのダウンロード |
allow-top-navigation |
親ページ(最上位のページ)を別のURLへ移動させる |
allow-top-navigation-by-user-activation |
ユーザーの操作がきっかけのときだけ、親ページを移動させる |
allow-top-navigation-to-custom-protocols |
http 以外のプロトコルへの移動 |
allow-pointer-lock |
Pointer Lock API(マウスポインターの固定) |
allow-orientation-lock |
画面の向きの固定 |
allow-presentation |
プレゼンテーションの開始 |
allow-storage-access-by-user-activation |
Storage Access API で、分離されていないCookieへのアクセスを求める |
制限されたときのメッセージ(実測)
スクリプト・フォーム・localStorage を使う子ページを、sandbox の値を変えて埋め込みました。
| sandbox の値 | スクリプト | フォーム送信 | localStorage | 枠の中のオリジン |
|---|---|---|---|---|
| (値なし) | 止まる | 止まる | ― | ― |
allow-scripts |
動く | 止まる | 使えない | null |
allow-scripts allow-same-origin allow-forms |
動く | 送信できた | エラーなし | (未確認) |
そのときコンソールに出たメッセージです。
Blocked script execution in 'https://child.example/script' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.
Blocked form submission to '/ok' because the form's frame is sandboxed and the 'allow-forms' permission is not set.
SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.
Ignored call to 'alert()'. The document is sandboxed, and the 'allow-modals' keyword is not set.
Blocked opening 'https://child.example/ok' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set.
Unsafe attempt to initiate navigation for frame with URL 'https://parent.example/parent' from frame with URL 'about:srcdoc'. The frame attempting navigation of the top-level window is sandboxed, but the flag of 'allow-top-navigation' or 'allow-top-navigation-by-user-activation' is not set.
メッセージに「どのトークンが足りないか」がそのまま書かれているので、必要なものだけを1つずつ足していけます。
sandbox の選び方
| 埋め込むもの | sandbox の例 |
|---|---|
| 表示だけの静的なHTML | sandbox(値なし) |
| スクリプトで動くウィジェット(別オリジン) | sandbox="allow-scripts" |
| 入力フォーム(別オリジン) | sandbox="allow-scripts allow-forms" |
| ログインが必要な別オリジンのページ | sandbox="allow-scripts allow-forms allow-same-origin" |
| リンクを新しいタブで開かせたい | 上記に allow-popups を足す |
MDN では、埋め込むページが親と同じオリジンの場合、allow-scripts と allow-same-origin を両方付けることは強く非推奨とされています。枠の中のスクリプトが親ページの iframe から sandbox 属性を外せてしまい、制限の意味がなくなるためです。また、sandbox を付けるとブラウザ内蔵の PDF ビューアーは働かない、とも記載されています。
allow属性:カメラや全画面などの機能を許可する
allow は、埋め込んだページに特定の機能(Permissions Policy)を使わせるための属性です。sandbox が「制限して一部を解除する」のに対し、allow は「既定では使えない機能を個別に許可する」役割です。
<iframe
src="https://example.com/meeting"
title="ビデオ通話"
allow="camera; microphone; fullscreen; display-capture"
width="800" height="600"
></iframe>
複数の機能は ;(セミコロン)で区切ります。代表的な値を挙げます(個々の機能の動作は今回未実行で、一般に知られた仕様の整理です)。
| 値 | 許可する機能 | よくある用途 |
|---|---|---|
fullscreen |
全画面表示 | 動画プレーヤー |
autoplay |
自動再生 | 動画・音声 |
picture-in-picture |
小窓での再生 | 動画プレーヤー |
encrypted-media |
保護されたメディアの再生 | 動画配信 |
camera |
カメラ | ビデオ通話 |
microphone |
マイク | ビデオ通話・音声入力 |
display-capture |
画面共有 | ビデオ通話 |
geolocation |
位置情報 | 地図・店舗検索 |
clipboard-write |
クリップボードへの書き込み | コピーボタン |
web-share |
共有メニューの呼び出し | 共有ボタン |
MDN では、allowfullscreen 属性は従来からの書き方とされ、allow="fullscreen" への置き換えが案内されています。
| 比べる点 | sandbox | allow |
|---|---|---|
| 考え方 | 全部を制限し、必要なものを解除 | 必要な機能を個別に許可 |
| 対象 | スクリプト・フォーム・ポップアップ・移動など | カメラ・マイク・全画面・位置情報など |
| 区切り | スペース | セミコロン |
| 書かないとき | 制限なし | 別オリジンの枠では多くの機能が使えない |
表示されない原因をエラーメッセージ別に切り分ける
「iframe を書いたのに枠が空のまま」「接続が拒否されましたと出る」というときは、ブラウザの開発者ツール(F12 キー)のコンソールを開くと、原因がメッセージで示されています。ここでは実際に再現したメッセージごとに、原因と直し方を並べます。
切り分けの流れ
flowchart TD
A[iframeが表示されない] --> B{コンソールにメッセージがある}
B -->|X-Frame-Options| C[埋め込まれる側が埋め込みを断っている]
B -->|frame-ancestors| D[CSPで埋め込み元が未許可]
B -->|Mixed Content| E[httpsのページにhttpを埋め込んでいる]
B -->|sandboxed| F[sandboxのトークンが不足]
B -->|メッセージなし| G{枠は出ているか}
G -->|枠はあるが中身が違う| H[Cookieが届かずログイン前の画面になっている]
G -->|枠自体が見えない| I[CSSで非表示や高さ0になっていないか確認]
原因1:X-Frame-Options で断られている
Refused to display 'https://child.example/' in a frame because it set 'X-Frame-Options' to 'deny'.
Refused to display 'https://child.example/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
埋め込まれる側のサーバーが、レスポンスヘッダーで「枠の中に表示しないでください」と指示しています。
| ヘッダーの値 | 意味 | 今回の結果 |
|---|---|---|
X-Frame-Options: DENY |
どこからの埋め込みも断る | 別オリジンから埋め込むと表示されない |
X-Frame-Options: SAMEORIGIN |
同じオリジンからの埋め込みだけ許可 | 別オリジンでは表示されず、同じオリジンでは表示された |
これは埋め込む側のHTMLでは変えられません。 相手のサイトが埋め込み用に用意しているURL(共有メニューの「埋め込む」で出てくるコードなど)を使うか、自分のサイト同士であれば埋め込まれる側のサーバー設定を変更します。MDN には、X-Frame-Options を meta 要素に書いても効果はなく、HTTPヘッダーとして送る必要があること、ALLOW-FROM は現在のブラウザでは無視されることが記載されています。
原因2:CSP の frame-ancestors で許可されていない
Refused to frame 'https://child.example/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'none'".
Refused to frame 'https://child.example/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'".
Content-Security-Policy ヘッダーの frame-ancestors は、X-Frame-Options より細かく「どのサイトからの埋め込みを許可するか」を指定できます。
| ヘッダーの値 | 意味 | 今回の結果 |
|---|---|---|
frame-ancestors 'none' |
どこからの埋め込みも断る | 表示されない |
frame-ancestors 'self' |
同じオリジンだけ許可 | 別オリジンでは表示されない |
frame-ancestors 'self' https://parent.example |
自サイトと parent.example を許可 | parent.example から埋め込むと表示された |
特定の相手サイトにだけ埋め込みを許可したいときは、3行目の形を使います。
原因3:混在コンテンツ(https のページに http を埋め込んだ)
Mixed Content: The page at 'https://parent.example/parent' was loaded over HTTPS, but requested an insecure frame 'http://child.example/ok'. This request has been blocked; the content must be served over HTTPS.
https のページの中に http のページは埋め込めません。src を https のURLに変えるのが直し方です。埋め込みたいページが https に対応していない場合は、そのページ側の https 対応が必要です。
原因4:sandbox のトークンが足りない
枠は表示されるのに、ボタンが反応しない・フォームが送れない・中身が空、というときは sandbox を確認します。メッセージは前の章のとおりで、'allow-scripts' permission is not set のように足りないトークンが書かれています。
原因5:枠の中に Cookie が送られていない
エラーは出ないのに、枠の中がログイン前の画面になる場合は Cookie を確認します。別サイト(child)で3種類の Cookie を設定し、parent のページの iframe の中で何が送られるかを調べました。
| Cookie の属性 | child を直接開いたとき | parent の iframe の中 |
|---|---|---|
| 属性なし(SameSite 指定なし) | 送られる | 送られない |
SameSite=None; Secure |
送られる | 送られる |
SameSite=None; Secure; Partitioned(直接開いて設定) |
送られる | 送られない |
SameSite=None; Secure; Partitioned(iframe の中で設定) |
送られない | 送られる |
Chromium 141(Playwright の既定設定)での結果です。SameSite を指定していない Cookie は、別サイトの iframe の中には送られませんでした。 埋め込み先でログイン状態を使いたい場合は、埋め込まれる側で SameSite=None; Secure を付ける必要があります。Partitioned を付けた Cookie は「どのサイトに埋め込まれたか」ごとに別々に保存されるため、直接開いて設定したものと、iframe の中で設定したものは共有されませんでした。
サードパーティ Cookie をブラウザ側で制限している設定では、SameSite=None; Secure でも送られない場合があります(この設定での確認は未実行です)。その場合に備える方法として、Partitioned 属性や Storage Access API が用意されています。
メッセージが出ないときの確認表
| 症状 | 確認すること |
|---|---|
| 枠が見えない | CSS で display: none、高さ 0、親要素の overflow: hidden になっていないか |
| 枠が小さい(300×150) | width / height や CSS の指定が効いているか |
| 高さだけ固定される | aspect-ratio と一緒に height: auto を書いたか |
| スクロールするまで出ない | loading="lazy" の動作(正常) |
| 404 などのエラーページが出る | src のURLを直接ブラウザで開いて確認 |
| 親から中身を操作できない | 別オリジンの枠は DOM にアクセスできない(後述の postMessage を使う) |
curl -I でヘッダーを確認する手順
ブラウザを開かなくても、埋め込みたいURLが埋め込みを許可しているかは curl -I(ヘッダーだけを取得)で確認できます。
curl -sI https://example.com/page | grep -iE 'x-frame-options|content-security-policy'
手元の nginx(後述の設定)に対して実行した結果です。
$ curl -I http://127.0.0.1:8514/index.html
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Content-Type: text/html
Content-Length: 41
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
(Date などの行は省いています。)grep で絞ると次の2行だけになります。
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
| 出力 | 読み取り方 |
|---|---|
| どちらの行も出ない | 埋め込みの制限なし |
X-Frame-Options: DENY |
埋め込めない |
X-Frame-Options: SAMEORIGIN |
同じオリジンからだけ埋め込める |
frame-ancestors 'none' |
埋め込めない |
frame-ancestors 'self' https://... |
自サイトと、書かれているサイトからだけ埋め込める |
サーバーによっては -I(HEAD リクエスト)に対応していないことがあります。その場合は curl -s -o /dev/null -D - URL で、通常の GET のヘッダーを表示できます(この書き方は今回未実行です)。
自サイトの埋め込み可否を設定する
ここからは「埋め込まれる側」の設定です。自分のサイトがほかのサイトの枠の中に表示されるのを防ぐと、透明な枠を重ねて意図しないクリックをさせる手口(クリックジャッキング)への対策になります。
nginx の設定例
server {
listen 80;
root /var/www/html;
# 既定:自サイト内だけに埋め込みを許可
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
# 外部サイトへの埋め込みを許可するページ
location = /widget.html {
add_header Content-Security-Policy "frame-ancestors 'self' https://partner.example" always;
}
}
nginx 1.24.0 で、同じ内容の設定(ポートと root は検証用に変更)を読み込んで確認した結果です。
$ curl -I http://127.0.0.1:8514/widget.html
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Content-Type: text/html
Content-Length: 41
Content-Security-Policy: frame-ancestors 'self' https://partner.example
/widget.html では、server ブロックに書いた X-Frame-Options が付いていません。 nginx の add_header は、location の中に1つでも add_header を書くと、上の階層のものを引き継がなくなるためです。今回の例では、外部に埋め込みを許可するページから X-Frame-Options: SAMEORIGIN が外れてちょうどよい結果ですが、ほかのヘッダーも一緒に消える点は覚えておくと役立ちます。
Apache の設定例
# mod_headers が有効な環境で、設定ファイルまたは .htaccess に記述
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"
# 特定のファイルだけ外部への埋め込みを許可
<Files "widget.html">
Header always unset X-Frame-Options
Header always set Content-Security-Policy "frame-ancestors 'self' https://partner.example"
</Files>
Header always set X-Frame-Options "SAMEORIGIN" の書き方は MDN の X-Frame-Options のページに載っている形です。Apache は今回の環境に入っていないため、この設定の動作確認は未実行です。
どちらのヘッダーを使うか
| 目的 | 設定 |
|---|---|
| どこにも埋め込ませない | X-Frame-Options: DENY と frame-ancestors 'none' |
| 自サイト内だけ許可 | X-Frame-Options: SAMEORIGIN と frame-ancestors 'self' |
| 特定のサイトにだけ許可 | frame-ancestors 'self' https://相手のサイト(X-Frame-Options は付けない) |
MDN では、現在のブラウザ向けには frame-ancestors のほうが包括的に制御できる方法として案内されています。
postMessageで高さを自動調整する
別オリジンのページを埋め込んだとき、親ページから枠の中の高さを直接読むことはできません。実際に試すと次のエラーになりました。
SecurityError: Failed to read a named property 'document' from 'Window': Blocked a frame with origin "https://parent.example" from accessing a cross-origin frame.
そこで、枠の中のページから「自分の高さ」を postMessage で親に伝え、親がそれを受け取って iframe の高さを変える方法を使います。両方のページを自分で編集できることが前提です。
埋め込まれる側(子ページ)
<script>
const PARENT_ORIGIN = "https://parent.example"; // 埋め込む側のオリジン
function sendHeight() {
const height = document.documentElement.scrollHeight;
parent.postMessage({ type: "iframe-height", height: height }, PARENT_ORIGIN);
}
// 中身の大きさが変わるたびに送る
new ResizeObserver(sendHeight).observe(document.documentElement);
window.addEventListener("load", sendHeight);
</script>
埋め込む側(親ページ)
<iframe id="embed" src="https://child.example/tall"
title="埋め込みコンテンツ" style="width:100%; border:0;" height="150"></iframe>
<script>
const CHILD_ORIGIN = "https://child.example";
const frame = document.getElementById("embed");
window.addEventListener("message", (event) => {
if (event.origin !== CHILD_ORIGIN) return; // 送信元を確認
if (event.source !== frame.contentWindow) return; // この iframe からのメッセージか確認
const data = event.data;
if (!data || data.type !== "iframe-height") return;
const height = Number(data.height);
if (!Number.isFinite(height) || height <= 0 || height > 10000) return;
frame.style.height = height + "px";
});
</script>
実行結果
| 状態 | iframe の高さ |
|---|---|
| HTML の初期値 | 150px |
| 子ページ(中身 900px)の読み込み後 | 900px |
| 子ページの中身を 1400px に広げた後 | 1400px |
別オリジン同士でも、中身の高さに合わせて枠が伸びることを確認できました。
postMessage の第2引数(送り先のオリジン)と、受け取る側の event.origin の確認はセットで書きます。 送り先のオリジンが実際の親と違う場合、メッセージは届かず、コンソールに次のように表示されました。
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://other.example') does not match the recipient window's origin ('https://parent.example').
| チェック | 理由 |
|---|---|
| 送る側は送り先のオリジンを明示する | 想定外のページに埋め込まれたとき、情報が渡らない |
受ける側は event.origin を確認する |
想定外のページからのメッセージを無視できる |
受ける側は event.source を確認する |
複数の枠があるとき、対象の枠を取り違えない |
| 値は数値に変換し、範囲を確認する | 想定外の値でレイアウトが崩れるのを防ぐ |
MDN の iframe 要素のページには、埋め込まれた文書の側からサイズを伝える新しい仕組み(frame-sizing プロパティなど)も紹介されています。今回は確認しておらず、幅広いブラウザで使える方法として postMessage を紹介しました。
廃止された属性の置き換え表
過去のHTMLで使われていた属性は、CSS や新しい属性に置き換えられます。MDN で非推奨とされているものの一覧です。
| 従来の属性 | 置き換え | 書き方 |
|---|---|---|
frameborder="0" |
CSS の border |
style="border: 0;" |
scrolling="no" |
CSS(埋め込まれる側で overflow を指定) |
子ページの body に overflow: hidden |
marginwidth / marginheight |
CSS(埋め込まれる側の余白) | 子ページの body に margin: 0 |
align |
CSS | float や display: block; margin: auto |
longdesc |
title 属性や本文での説明 |
title="..." |
allowfullscreen |
allow 属性 |
allow="fullscreen" |
Chromium 141 では、frameborder="0" を付けた iframe の枠線は 0px になり、現在も働いていました。新しく書くときは CSS の border: 0 にそろえておくと、現在の HTML の仕様に沿った書き方になります。
<!-- 従来の書き方 -->
<iframe src="https://example.com/embed" width="560" height="315"
frameborder="0" scrolling="no" allowfullscreen></iframe>
<!-- 置き換えた書き方 -->
<iframe src="https://example.com/embed" title="紹介動画"
width="560" height="315" loading="lazy"
allow="fullscreen" style="border: 0;"></iframe>
コピペ用テンプレート集
動画の埋め込み(レスポンシブ)
<iframe
src="https://example.com/embed/VIDEO_ID"
title="動画のタイトル"
width="560" height="315"
loading="lazy"
allow="fullscreen; picture-in-picture; encrypted-media"
referrerpolicy="strict-origin-when-cross-origin"
style="display:block; width:100%; height:auto; aspect-ratio:16/9; border:0;"
></iframe>
地図の埋め込み
<iframe
src="https://example.com/maps/embed?q=..."
title="アクセスマップ"
width="600" height="450"
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
style="display:block; width:100%; max-width:600px; height:auto; aspect-ratio:4/3; border:0;"
></iframe>
別オリジンのウィジェット(制限つき)
<iframe
src="https://widget.example/embed"
title="お問い合わせフォーム"
width="400" height="600"
loading="lazy"
sandbox="allow-scripts allow-forms"
style="border:0; width:100%; max-width:400px;"
></iframe>
よくある質問
Q. 「接続が拒否されました」と表示されて埋め込めません。
A. 埋め込まれる側が X-Frame-Options か CSP の frame-ancestors で埋め込みを制限しています。コンソールに Refused to display または Refused to frame と出ているか確認し、curl -I でヘッダーを見ると確実です。埋め込む側のHTMLでは変えられないため、相手のサイトが提供している埋め込み用のURLを使います。
Q. iframe の高さを中身に合わせて自動で変えられますか?
A. 同じオリジンなら親から中身の高さを読めます。別オリジンの場合は、子ページから postMessage で高さを送り、親が受け取って設定します。今回の確認では、中身 900px・1400px に合わせて枠の高さが変わりました。
Q. sandbox は必ず付けたほうがよいですか? A. 自分で管理していないページや、ユーザーが入力したHTMLを表示する場合は付けておくと安心です。動画や地図などの公式の埋め込みコードは、sandbox を付けるとスクリプトが止まって表示されなくなることがあるため、提供元の案内に合わせます。
Q. width を 100% にしたら高さが変わらず、縦横比が崩れました。
A. HTML の height 属性が残っているためです。CSS に height: auto と aspect-ratio: 16 / 9 を追加すると、幅に合わせて高さが決まります。実測では、幅 375px のとき 375×211px になりました。
まとめ
- iframe は
srcとtitleを書くのが基本。サイズ指定なしでは 300×150px、2px の枠線が付く - レスポンシブ対応は
width: 100%; height: auto; aspect-ratio: 16 / 9。padding-top 方式と同じ結果を、入れ物なしで書ける - 表示されないときはコンソールを見る。
X-Frame-Options、frame-ancestors、Mixed Content、sandboxedのどれが出ているかで原因がわかる - 埋め込めるかどうかは
curl -Iで事前に確認できる。制限は埋め込まれる側のヘッダーで決まる - sandbox は値なしで全制限、トークンで個別に解除。足りないトークンはエラーメッセージに書かれている
- 画面外の埋め込みには
loading="lazy"。近づくまで読み込みを待たせられる - 別オリジンの高さ調整は postMessage。送り先のオリジン指定と
event.originの確認をセットで書く
参考・出典
- MDN Web Docs: iframe 要素(属性、sandbox のトークン、非推奨の属性)
- MDN Web Docs: X-Frame-Options(値、Apache / nginx の設定例)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「HTML iframeの使い方|レスポンシブ・sandbox・表示されない原因」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。