Тег iframe (от английского inline frame, «встроенный фрейм») открывает внутри страницы окно с другой HTML-страницей. У вложенной страницы свой документ, свои стили, свои скрипты и своя прокрутка, поэтому CSS и JavaScript основной страницы на неё не действуют. Так на сайты попадают карты, ролики, формы оплаты, виджеты и реклама. Примеры в блоках «Результат» ниже устроены так же: каждый из них работает в собственном iframe.
Дальше разобраны атрибуты, src и srcdoc, размер и рамка, iframe для видео, ограничения через sandbox, разрешения allow, ленивая загрузка, связь через name и target, работа из JavaScript и причины, по которым нужная страница в iframe не показывается. Другие теги группы собраны в шпаргалке по тегам.
<iframe src="https://example.com" width="400" height="200"
title="Пример страницы"></iframe>
В этой статье
- Тег iframe в HTML: что это и для чего нужен
- Атрибуты тега iframe: все основные атрибуты в одной таблице
- Атрибут src тега iframe и атрибут srcdoc: что покажет iframe
- Размер и рамка тега iframe: width, height и CSS
- Тег iframe для видео: как встроить ролик на страницу
- Атрибут sandbox тега iframe: ограничения для встроенной страницы
- Атрибуты allow, allowfullscreen и referrerpolicy тега iframe
- Атрибут loading тега iframe: ленивая загрузка
- Атрибут name тега iframe и target: как открыть ссылку внутри iframe
- Тег iframe в JavaScript: contentWindow и postMessage
- Почему тег iframe не работает: X-Frame-Options и frame-ancestors
Тег iframe в HTML: что это и для чего нужен
Сначала о том, что именно показывает iframe и чем он отличается от обычного блока на странице.
Внутри iframe открыт отдельный документ: у него свой <html>, свой <body> и свой адрес. Стиль p { color: red } на основной странице не покрасит абзацы внутри, а переменная JavaScript на основной странице не видна вложенной. Это и удобно, и неудобно: чужой виджет не сломает вашу вёрстку, но и подогнать его под стиль сайта обычным CSS не получится.
Закрывающий тег </iframe> обязателен. Самозакрывающуюся запись <iframe src="..." /> браузер читает как открывающий тег: всё, что записано после неё, до ближайшего </iframe> становится текстом внутри тега. Абзац после такого iframe пропадает, а следующий iframe перестаёт быть отдельным элементом. Текст между открывающим и закрывающим тегом при обычной записи тоже не показывается: размер окна с запасным текстом и без него одинаков.
<iframe src="map.html" title="Карта проезда"></iframe>
<p>Этот абзац виден, потому что тег закрыт.</p>
Пример: размер и рамка тега iframe по умолчанию
Внутри окна настоящий документ со своей прокруткой. Кнопки меняют размер и рамку, строки показывают, что получилось. Размер по умолчанию 300 на 150 px, а рамка прибавляет ещё по 2 px с каждой стороны.
Для чего нужен iframe, понятно из типичных задач:
- внешний сервис: карта, видеоплеер, форма оплаты или чат, за которые отвечает другой сайт и которые нельзя собрать из своих тегов;
- изоляция: код стороннего виджета или рекламы не должен менять вашу страницу, а при необходимости его можно ограничить через
sandbox; - демонстрации кода: пример со своими стилями и скриптами работает рядом с текстом и не влияет на него;
- показ готового документа: PDF или другая страница вашего сайта в рамке.
Свои страницы одного сайта в iframe обычно не собирают: для общих блоков проще подключение на сервере. Каждый iframe — это ещё одна полноценная загрузка страницы со своими файлами, и чем их больше, тем медленнее открывается основная страница.
Атрибуты тега iframe: все основные атрибуты в одной таблице
Атрибутов много, но почти все нужны только в особых случаях: адрес, размер и название нужны почти всегда.
<iframe src="https://example.com/widget"
width="400" height="300"
title="Виджет погоды"
loading="lazy"
sandbox="allow-scripts"></iframe>
| Атрибут | Что делает | Пример записи |
|---|---|---|
src | Адрес страницы, которую нужно открыть внутри | src="widget.html" |
srcdoc | HTML-код страницы прямо в значении атрибута. Если задан, src не используется | srcdoc="<p>Привет</p>" |
width, height | Размер окна в пикселях. По умолчанию 300 на 150 | width="400" height="200" |
title | Название содержимого. Рекомендация WCAG (приём H64) советует давать название каждому iframe | title="Карта проезда" |
name | Название окна, на которое указывает target у ссылки или формы | name="okno" |
sandbox | Ограничивает вложенную страницу: скрипты, формы, окна, переходы | sandbox="allow-scripts" |
allow | Разрешает вложенной странице возможности браузера: камеру, геолокацию, полный экран | allow="camera; fullscreen" |
allowfullscreen | Разрешает полноэкранный режим. То же, что allow="fullscreen" | allowfullscreen |
referrerpolicy | Какой адрес основной страницы отправлять серверу вложенной | referrerpolicy="no-referrer" |
loading | Значение lazy откладывает загрузку, пока окно не приблизится к экрану | loading="lazy" |
credentialless | Экспериментальный атрибут: поддерживается не везде, перед использованием уточните его поддержку | credentialless |
Устаревшие атрибуты встречаются в старом коде. Часть из них браузеры ещё понимают, но писать их в новой вёрстке не нужно.
| Атрибут | Что делал | Чем заменить |
|---|---|---|
frameborder | Включал или убирал рамку. Значение 0 обнуляет ширину рамки | border: 0; в CSS |
scrolling | Управлял полосами прокрутки. Значение no убирает полосу внутри окна | Стиль overflow во вложенной странице: на сам iframe он полосу не убирает |
seamless | Должен был сливать окно со страницей, но не поддерживается | Ничем, атрибут не работает |
marginwidthmarginheight | Отступы внутри окна | body { margin: 0; } во вложенной странице |
align | Обтекание и выравнивание | float и vertical-align в CSS |
Атрибут src тега iframe и атрибут srcdoc: что покажет iframe
Адрес вложенной страницы задаёт src, а готовый HTML-код можно записать прямо в srcdoc.
В src записывают адрес: полный с https:// или относительный, как у ссылки. Основы этого атрибута разобраны в уроке iframe и src. Значение about:blank открывает пустую страницу, а без src окно остаётся пустым.
<iframe src="https://example.com/widget.html"></iframe>
<iframe src="widget.html"></iframe>
<iframe src="/pages/widget.html"></iframe>
В srcdoc записывают сам документ. Внутрь значения нельзя писать двойные кавычки, поэтому их заменяют на " или используют одинарные. Адрес такого документа — about:srcdoc, а источник наследуется от страницы, где записан тег: родитель может читать содержимое вложенного документа, а вложенный документ может читать родителя.
<iframe srcdoc="<h3>Привет</h3><p>Страница из srcdoc</p>">
</iframe>
Если записаны оба атрибута, побеждает srcdoc. Браузер даже не обращается к серверу за файлом из src: журнал сервера остаётся пустым. Стоит убрать srcdoc, и страница по src загружается. Открытая по src страница другого сайта для родителя закрыта: contentDocument равен null. Адрес со схемой data: тоже считается чужим.
Пример: атрибуты src и srcdoc тега iframe вместе
Четыре кнопки собирают окно с разными атрибутами. Страница из src читается родителем как закрытая, а страница из srcdoc имеет адрес about:srcdoc. Когда записаны оба атрибута, показан srcdoc.
Размер и рамка тега iframe: width, height и CSS
Размер задают атрибутами или CSS, а рамку убирают стилем border: 0, а не атрибутом frameborder.
Без width и height окно получает размер 300 на 150 пикселей, а к нему браузер добавляет рамку 2px inset, так что полный размер 304 на 154. Атрибуты width="400" height="100" задают размер содержимого, поэтому с рамкой получается 404 на 104. Допустимы и проценты: width="50%" взял половину ширины страницы. CSS перекрывает атрибуты.
Ширина 100% у окна с рамкой даёт сюрприз: рамка прибавляется сверху к ширине, и iframe выступает за контейнер на 4 px. Лечится box-sizing: border-box или нулевой рамкой. Адаптивное окно с нужным соотношением сторон получается из aspect-ratio: при ширине 100% и height: auto высота считается сама. Без aspect-ratio такая же запись даёт высоту 150.
iframe {
display: block;
width: 100%;
max-width: 560px;
aspect-ratio: 16 / 9;
height: auto;
border: 0;
}
По умолчанию iframe строчный и стоит на базовой линии текста, поэтому под ним остаётся зазор из-за выравнивания по базовой линии строки. В примере ниже он составляет несколько пикселей. Его убирает display: block или vertical-align: top.
Пример: размер, рамка и CSS тега iframe
Контейнер скрывает всё, что выступает за его край. Кнопки по очереди добавляют стили, а числа показывают размер окна, выступ вправо и зазор снизу. Размер окна подсчитан вместе с рамкой.
Высоту под содержимое iframe сам не подбирает: если вложенная страница выше окна, внутри появляется полоса прокрутки. Подогнать высоту можно только скриптом через postMessage, об этом раздел про JavaScript. Полоса внутри окна отключается атрибутом scrolling="no" (он устарел, но браузеры его выполняют), а стиль overflow: hidden на самом iframe её не убирает.
Тег iframe для видео: как встроить ролик на страницу
Видеохостинги выдают готовый код для встраивания: в нём адрес плеера, размер, название и разрешения.
Видео с YouTube встраивают через iframe: на странице ролика кнопка «Поделиться», затем «Встроить» даёт готовую запись. Адрес в ней строится из слова embed и кода ролика (VIDEO_ID), а разрешения вынесены в allow.
<iframe width="560" height="315"
src="https://www.youtube.com/embed/VIDEO_ID"
title="Название ролика"
allow="fullscreen; picture-in-picture"
loading="lazy"></iframe>
Параметры плеера дописывают к адресу после знака ?. По документации YouTube start задаёт секунду начала, end — секунду остановки, autoplay=1 включает воспроизведение при загрузке (браузеры могут не разрешить его со звуком), controls=0 убирает панель управления, а loop=1 повторяет ролик (для одного видео вместе с параметром playlist).
<iframe width="560" height="315" title="Фрагмент ролика"
src="https://www.youtube.com/embed/VIDEO_ID?start=30&end=90"
allow="fullscreen"></iframe>
Чтобы ролик тянулся по ширине и держал пропорции, iframe оформляют правилами из раздела о размере: width: 100% и aspect-ratio: 16 / 9. Если видеофайл находится на вашем сайте, iframe не нужен: для него есть тег video.
Атрибут sandbox тега iframe: ограничения для встроенной страницы
Атрибут sandbox по умолчанию запрещает почти всё, а нужное разрешают по одному ключевому слову.
Слово sandbox значит «песочница». Запись sandbox без значения (то же, что sandbox="") включает все ограничения сразу, а слова в значении снимают их по одному. Принцип такой: разрешить только то, без чего вложенная страница не работает. Подробнее про пустое значение рассказано в уроке атрибут sandbox.
<iframe src="https://example.com/widget"
sandbox="allow-scripts allow-forms"></iframe>
| Ключевое слово | Что разрешает | Что бывает без него |
|---|---|---|
allow-scripts | Запуск скриптов во вложенной странице | Скрипты и обработчики onclick не выполняются |
allow-same-origin | Считать страницу «своей» по источнику: доступ к хранилищу, куки и, если адрес со своего сайта, к родителю | Источник становится null, чтение хранилища и куки даёт ошибку SecurityError |
allow-forms | Отправку форм | Нажатие на кнопку отправки ничего не делает |
allow-popups | Открытие новых окон скриптом и ссылкой | Новое окно не открывается, window.open() возвращает null |
allow-modals | Окна alert, confirm, prompt | Окно не появляется, confirm сразу возвращает false |
allow-top-navigation | Переход всей страницы по ссылке с target="_top" | Переход заблокирован |
allow-top-navigation-by-user-activation | Тот же переход, но только после клика | Переход заблокирован |
allow-popups-to-escape-sandbox | Открытые окна не наследуют ограничения1 | Окна открываются с теми же ограничениями |
allow-downloads, allow-pointer-lock, allow-orientation-lock, allow-presentation | Скачивание файлов, захват указателя, блокировку ориентации, показ на втором экране1 | Соответствующая возможность выключена |
1 Узнать, знает ли браузер ключевое слово, можно методом sandbox.supports(). Слово allow-top-navigation-to-custom-protocols поддерживается не везде.
Самое важное в этой теме: iframe без sandbox может увести всю страницу. Ссылка с target="_top" во вложенной странице без атрибута sandbox заменяет адрес основной страницы. Пустой sandbox и allow-popups такой переход блокируют, а allow-top-navigation и allow-top-navigation-by-user-activation его возвращают. Поэтому чужие виджеты и рекламу без нужды не подключают без sandbox.
Пример: атрибут sandbox тега iframe и его ключевые слова
Пять кнопок задают значение sandbox. Внутри окна три кнопки: скрипт, чтение родителя и форма, которая уводит окно на пустую страницу. Строка снизу считает загрузки документа внутри.
Сочетание allow-scripts и allow-same-origin у страницы с того же сайта сводит защиту на нет. Такая страница получает доступ к frameElement и может убрать атрибут sandbox у собственного iframe. Для чужого адреса оба слова вместе допустимы, потому что источник чужой, а allow-same-origin лишь сохраняет ему его собственное хранилище.
Атрибуты allow, allowfullscreen и referrerpolicy тега iframe
Возможности браузера вложенной странице разрешает allow, а Referer ограничивает referrerpolicy.
Страница с другого сайта по умолчанию не получает доступ к камере, геолокации и полноэкранному режиму. Страница чужого источника в iframe без allow получает отказ по всем трём, а страница с того же источника может запросить доступ. Названия возможностей записывают через точку с запятой.
<iframe src="https://example.com/call"
allow="camera; microphone; fullscreen"></iframe>
Полноэкранный режим включают двумя способами: allow="fullscreen" и старым булевым атрибутом allowfullscreen. Результат одинаков: разрешено только полноэкранное окно, камера и геолокация остаются запрещёнными.
Пример: атрибуты allow и allowfullscreen тега iframe
Вложенная страница с адресом data: считается чужой и сама сообщает родителю, что ей разрешено. Кнопки по очереди задают allow и allowfullscreen.
Атрибут referrerpolicy решает, что сервер вложенной страницы узнает о вашей. Допустим, адрес основной страницы содержит параметр ?secret=1. Таблица показывает, что при этом попадает в заголовок Referer на сервере вложенной страницы с другого сайта.
| Значение | Что получает сервер | Пример записи |
|---|---|---|
| Без атрибута | Только источник страницы, без пути и параметров (на своём сайте — полный адрес) | src="..." |
no-referrer | Заголовок не отправлен | referrerpolicy="no-referrer" |
origin | Только источник | referrerpolicy="origin" |
same-origin | Для другого сайта заголовок не отправлен | referrerpolicy="same-origin" |
unsafe-url | Полный адрес с параметрами, но без части после # | referrerpolicy="unsafe-url" |
Если в адресе основной страницы есть что-то личное (код заказа, ключ доступа), для сторонних виджетов referrerpolicy="no-referrer" безопаснее.
Атрибут loading тега iframe: ленивая загрузка
Атрибут loading="lazy" откладывает загрузку iframe, пока окно не приблизится к видимой части страницы.
Тяжёлые окна вроде плеера или карты загружают много данных ещё до того, как читатель дошёл до них. Значение lazy переносит эту загрузку на момент, когда окно близко к экрану. По оценке web.dev, встроенный ролик YouTube это порядка 500 КБ. Значение по умолчанию eager загружает окно сразу.
<iframe src="https://example.com/map" loading="lazy"
width="600" height="400" title="Карта"></iframe>
Как это работает на практике. Окно с lazy, которое находится далеко внизу страницы, при открытии не запрашивается, но подгружается заранее, не дожидаясь, пока до него долистают: в испытании на быстром соединении окна на расстоянии 900, 1500 и 2500 px от верха страницы загрузились сразу, а окно в 4000 с лишним пикселях под экраном нет. Порог выбирает браузер и зависит от скорости соединения, поэтому цифры стоит читать как пример, а не как правило.
Скрытое окно ведёт себя по-разному. Обычный iframe с display: none загружается сразу, хотя его никто не видит. Такой же iframe с loading="lazy" не загружается, пока его не показать. Поэтому для окон в скрытых вкладках и всплывающих блоках lazy экономит заметный объём.
Атрибут name тега iframe и target: как открыть ссылку внутри iframe
Название окна в name позволяет направить ссылку или форму в нужный iframe через target.
У ссылки и формы есть атрибут target: он говорит, где открыть результат. Если в нём записано значение name какого-то iframe, страница откроется внутри него, а основная страница не изменится. Так делают меню, где ссылки меняют содержимое одного окна.
<a href="page1.html" target="okno">Страница 1</a>
<iframe name="okno" src="start.html" title="Окно"></iframe>
Форма ведёт себя так же: <form target="okno"> отправляет результат в это окно. Если окна с таким названием нет, ссылка откроется в новой вкладке . Готовые значения _self, _parent, _top и _blank задают текущее окно, родителя, верхнюю страницу и новую вкладку.
Пример: атрибут name тега iframe и target у ссылки
Ссылка и форма с target=okno открывают страницу внутри окна, основная страница остаётся на месте. Строка сверху показывает, что window.frames.okno указывает на тот же iframe.
Окно с name доступно из JavaScript по названию: window.frames.okno и iframe.contentWindow дают один и тот же объект. По номеру тоже можно: window.frames[0] — это первый iframe страницы.
Тег iframe в JavaScript: contentWindow и postMessage
Из JavaScript вложенное окно доступно через contentWindow и contentDocument, но только если страница своего источника.
У тега есть два свойства: contentWindow (окно вложенной страницы) и contentDocument (её документ). Их возможности зависят от того, откуда загружена страница. Со своего источника читается и меняется всё. С чужого закрыто почти всё: правило браузера, политика одного источника, не пускает скрипты одной страницы в документ другой.
| Что делает скрипт | Свой источник | Чужой источник |
|---|---|---|
frame.contentDocument | Документ | null |
frame.contentWindow.document | Документ | SecurityError |
frame.contentWindow.location.href | Адрес читается | SecurityError |
frame.contentWindow.location.href = "..." | Открывает новый адрес | Тоже открывает: запись разрешена, чтение нет |
frame.contentWindow.postMessage(...) | Работает | Работает: так общаются две страницы |
frame.contentWindow.closed | false | Тоже читается |
Обмен между страницами разных источников идёт через postMessage: одна страница отправляет сообщение, другая слушает событие message. Сообщение приходит вместе с источником отправителя в event.origin (для страницы со схемой data: и для sandbox без allow-same-origin это строка null) и ссылкой на окно в event.source. Получатель обязан проверить, что сообщение пришло от нужного окна.
const frame = document.querySelector("iframe");
window.addEventListener("message", (e) => {
if (e.source !== frame.contentWindow) return;
frame.style.height = e.data.h + "px";
});
// внутри вложенной страницы
parent.postMessage(
{ h: document.documentElement.scrollHeight },
"*"
);
Пример: тег iframe в JavaScript и сообщения postMessage
Первые две кнопки сравнивают чтение документа у страницы с того же источника и с чужого. Третья кнопка показывает подбор высоты: кнопка внутри окна добавляет строку и отправляет новую высоту родителю.
Вместо "*" при отправке в чужое окно указывают точный источник получателя, например "https://widget.example". Так сообщение не попадёт на другую страницу, если окно успели перенаправить. Событие load у iframe срабатывает после загрузки вложенной страницы, и с него удобно начинать работу с contentDocument своего источника.
Почему тег iframe не работает: X-Frame-Options и frame-ancestors
Если страница в iframe не открывается, чаще всего сайт сам запретил встраивание заголовком ответа.
Многие сайты, от соцсетей до банков, не разрешают показывать себя внутри чужих страниц, чтобы никто не подсунул читателю поддельное окно с настоящим сайтом. Запрет задаёт сервер в заголовке ответа, и со стороны вашей страницы его не обойти. Два заголовка:
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://a.example
Заголовок X-Frame-Options старый и знает два значения: DENY (никому) и SAMEORIGIN (только страницам того же сайта). Новый способ — это директива frame-ancestors в заголовке Content-Security-Policy: в ней можно перечислить сайты, которым встраивание разрешено. Вот что получается:
| Заголовок сервера | Страница с другого сайта | Страница с того же сайта |
|---|---|---|
| Нет заголовка | Загружается | Загружается |
X-Frame-Options: DENY | Не показана | Не показана |
X-Frame-Options: SAMEORIGIN | Не показана | Загружается |
frame-ancestors 'none' | Не показана | Не показана |
frame-ancestors 'self' | Не показана | Загружается |
frame-ancestors с адресом вашего сайта | Загружается | Загружается |
frame-ancestors с чужим адресом | Не показана | Не показана |
X-Frame-Options: DENY и frame-ancestors * | Загружается, главнее frame-ancestors | Загружается |
В обоих случаях картина одна: окно остаётся пустым, документ закрыт (contentDocument равен null), а событие load у тега всё равно срабатывает, так что по нему отказ не обнаружить. Заголовки работают только как заголовки ответа: те же X-Frame-Options и frame-ancestors, записанные в теге <meta http-equiv>, браузер их игнорирует, и страница загружается в окно.
Что делать, если нужная страница запрещает встраивание: дать ссылку, которая открывается в новой вкладке, взять у сервиса официальный код для встраивания (у карт и видео он есть) или, если страница ваша, разрешить встраивание через frame-ancestors для нужных адресов. Другие причины пустого окна: неверный адрес src, страница с кодом ответа 404 (внутри увидите страницу ошибки), sandbox без нужных ключевых слов (скрипты и формы молчат), закрывающий тег пропущен или записан как самозакрывающийся.
Частые ошибки с тегом iframe
Основные ошибки связаны с закрывающим тегом, размером, рамкой, ограничениями и ожиданием, что чужая страница откроется.
| Ошибка | Что происходит | Как исправить |
|---|---|---|
<iframe src="a.html" /> | Браузер не видит закрытия: всё до ближайшего </iframe> становится текстом внутри тега | Добавить закрывающий тег </iframe> |
<iframe src="a.html"> | Закрывающий тег пропущен: то же самое, остальная страница пропадает | Всегда закрывать тег |
<iframe src="..."> | Нет title: окно остаётся без названия, а WCAG рекомендует его | Добавить title="..." по содержимому |
<iframe frameborder="0"> | Устаревший атрибут: браузеры пока понимают его, но он может перестать работать | Записать border: 0 в CSS |
iframe { width: 100%; } | Рамка 2 px добавляется к ширине, окно выступает за контейнер на 4 px | Добавить box-sizing: border-box или border: 0 |
height: auto | Без aspect-ratio высота остаётся 150, окно под содержимое не растёт | Задать aspect-ratio или передать высоту через postMessage |
src и srcdoc | Побеждает srcdoc, src игнорируется | Записать один из атрибутов |
Чужой виджет без sandbox | Страница внутри может увести основную по target="_top" | Добавить sandbox и нужные ключевые слова |
sandbox со словами allow-scripts и allow-same-origin | Для страницы своего сайта защита фактически выключена | Убрать allow-same-origin или не ограничивать свой код |
frame.contentDocument.title | Для чужого источника contentDocument равен null, ошибка | Читать данные через postMessage |
| Встраивание чужой страницы, которая его запретила | Окно остаётся пустым, событие load всё равно приходит | Дать ссылку или взять официальный код для встраивания |
<meta http-equiv> | Заголовок в теге meta не работает | Настроить заголовок на сервере |