嵌入和iframe

有时你想在页面上展示的内容已经存在于别处:YouTube上的视频、地图上的位置、由另一家公司运营的支付表单。与其重新构建,不如将整个页面放在你的页面内。实现这一功能的元素是<iframe>,它附带了一套控制,用于保持借用内容的大小、易用性和安全性。
iframe元素
iframe 将另一个网页嵌入到你自己的网页中。名称是"inline frame"(内联框架)的缩写,这很好地描述了它的作用:你页面上的一个小框架窗口,完全独立的页面通过它显示出来。博客文章中的YouTube视频几乎总是一个iframe。
你通过src属性指向想要显示的页面,这与图像使用的属性相同:
<iframe src="https://www.youtube.com/embed/aqz-KE-bpKQ"></iframe>这个单独的标签把YouTube播放器拉到你的页面上。可以想象成在墙上挂一扇窗户:墙是你的页面,通过窗户你看到一个完全不同的房间。
<iframe> 加载一个独立的、完整的网页,并将其显示为你的页面内的矩形。src属性保存该页面的地址,框架内的所有内容,其自己的HTML、CSS和JavaScript,都各自独立运行,与你的页面保持隔离。
<iframe src="https://www.openstreetmap.org/export/embed.html"></iframe>你在开闭标签之间写的任何内容都是备选内容,仅由过于陈旧而不支持框架的浏览器显示,实际上现在没有这样的浏览器。框架完全通过其属性进行配置,下一部分介绍你实际会设置的那些属性。
<iframe> 创建了一个**嵌套浏览上下文**:一个嵌入在你的文档中的完整、独立文档(浏览上下文是单个页面运行的环境,具有自己的历史记录、自己的全局作用域和自己的DOM副本)。框架在任何有意义的意义上都不是你的页面的DOM树的一部分;它是一个独立的页面,恰好被绘制在你的页面上的一个框中。
这种分离正是重点所在,它由**同源策略**强制执行:你的页面和被框架化的页面只有在共享一个源的情况下才能相互读取或编写脚本(源是方案、主机和端口的组合,因此https://example.com和https://other.com是不同的源)。你可以通过iframe.contentWindow用JavaScript到达同源框架;跨源框架是你可以指向但无法检查的密封盒子。几乎每个你放置的嵌入内容,一个视频、一张地图、一个小部件,都是跨源的,这正是为什么放置它们完全是安全的。
<iframe>在你的页面内显示整个其他网页,像是一扇小窗户看向另一个房间。你用src指向某个地方,就像图像做的那样。博客上的那个YouTube播放器?几乎总是这样的东西。 <iframe>加载一个独立的、自包含的页面,并在你的页面上以一个框显示它。它自己的HTML、CSS和JavaScript与你的页面隔离。你完全通过属性来设置它,从src开始。 嵌入视频、地图和小部件
好消息:你很少需要手动编写嵌入代码。你嵌入内容的网站会为你编写。在YouTube上你点击"分享",然后"嵌入",它会交给你一个现成的<iframe>来复制。地图、音乐播放器和日历都以相同的方式工作。你复制它们的块并将其粘贴到你的HTML中。
<iframe src="https://www.youtube.com/embed/aqz-KE-bpKQ" width="560" height="315"></iframe>width和height设置框架的大小,以像素为单位测量。改变这两个数字,视频在你的页面上就会变大或变小。
大多数嵌入代码直接来自源。寻找"分享"或"嵌入"按钮,该服务会给你一个为你调整大小和指向的<iframe>块。有三个属性值得理解,而不仅仅是粘贴:
<iframe
src="https://www.youtube.com/embed/aqz-KE-bpKQ"
width="560"
height="315"
title="Big Buck Bunny短片"
loading="lazy"
></iframe>width和height以像素为单位设置框架的大小。**title**属性为屏幕阅读器命名框架,它们宣布它的方式就像宣布链接的文本一样,所以一个没有标题的框架被读作一个无标签的"frame",对任何人都没有帮助。loading="lazy"告诉浏览器在读者滚动到它附近之前暂停下载被框架化的页面,这使得页面下方的嵌入内容不会减慢初始加载。
对于应该拉伸以适应其容器而不是固定像素大小的框架,删除width和height属性,并使用CSS调整其大小,使用aspect-ratio以保持视频的正确比例。
服务提供给你的嵌入片段是一个合理的默认值,而不是一个最终决定。几个属性决定了它在生产中的表现。
<iframe
src="https://www.youtube-nocookie.com/embed/aqz-KE-bpKQ"
title="Big Buck Bunny短片"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
style="width: 100%; aspect-ratio: 16 / 9; border: 0;"
></iframe>title在实践中不是可选的:辅助技术(如屏幕阅读器这样的软件会朗读页面)将框架视为地标并宣布其标题,所以缺少一个会在阅读顺序中留下一个空白区域。loading="lazy"延迟框架文档的加载,直到它接近视口(页面的可见区域),这在这里很重要,因为每个框架都是一个完整的页面加载,而不是单个资产。**referrerpolicy**控制在Referer标头(浏览器附加用于告诉服务器请求来自何处的字段)中发送给嵌入服务的页面URL的多少,收紧它会泄露更少关于你的用户的信息。使用aspect-ratio而不是固定像素进行响应式调整大小,以便框架在小屏幕上重新排列。一个值得保持的习惯:在粘贴前阅读片段,因为你继承了提供商选择的任何默认值。
<iframe>来粘贴。改变width和height来调整大小。这就是大部分的工作。 title以便屏幕阅读器可以命名框架,并添加loading="lazy"以便页面下方的框架不会减慢首次绘制。对于灵活的调整大小,删除像素宽度和高度,使用CSSaspect-ratio。 title用于阅读顺序,loading="lazy"因为框架是一个完整的页面加载而不是一个资产,一个紧密的referrerpolicy以便你泄露更少关于你的用户的信息,以及aspect-ratio用于响应式调整大小。阅读你粘贴的内容,因为你继承他们选择的每个默认值。 沙箱和权限
因为框架运行另一个完整的页面,你可能想限制该页面被允许做的事情。**sandbox**属性正是这样做的:它将框架放在一个安全围栏后面。
<iframe src="widget.html" sandbox></iframe>仅使用sandbox,被框架化的页面被限制在最严格的设置中:它不能运行自己的脚本、提交表单或弹出新窗口。然后你一次一个地交还特定的权限,仅那些嵌入内容实际需要的权限。想象成借给某人一间房间,但锁上你不想被打开的柜子。
两个属性控制框架被允许做什么。sandbox限制框架,allow授予对设备和浏览器功能的访问。
添加没有值的sandbox会阻止被框架化的页面可以做的几乎所有事情:脚本、表单、弹窗,以及将自己视为同源。你通过列出以空格分隔的权限令牌来放宽它:
<iframe
src="https://example.com/widget"
sandbox="allow-scripts allow-forms"
></iframe>allow-scripts让框架运行JavaScript,allow-forms让它提交表单,allow-popups让它打开新窗口。仅授予嵌入内容需要的权限。独立的**allow**属性管理敏感的浏览器功能,如全屏、摄像头和麦克风:
<iframe src="https://example.com/call" allow="camera; microphone; fullscreen"></iframe>如果功能未在allow中列出,被框架化的页面无法使用它,即使其自己的代码请求也不行。
两个不同的模型在这里,值得分开来看。sandbox从框架中移除功能;allow授予对受限功能的访问。它们不是同一事物的两种拼写。
sandbox没有值时一次应用所有限制:没有脚本、没有表单提交、没有弹窗、没有自动播放媒体,重要的是,框架被强制进入一个独特的不透明源(一个与其他任何东西都不匹配的源,所以框架不能声称与任何人(包括加载它的网站)是同源的)。你用令牌添加回功能:
<iframe
src="https://example.com/widget"
sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox"
></iframe>有一个组合要避免。同时授予allow-scripts和allow-same-origin给从你自己的源加载的框架,可以让被框架化的页面运行能够回出并移除自己沙箱的代码,这就违反了初衷。只有在框架的内容完全受信任时才配对这两个。
allow属性是一个**权限策略**(一个浏览器机制,用于决定哪些页面可能使用敏感功能,如摄像头、麦克风、地理位置或全屏)。其默认值是拒绝:一个未在allow中命名的功能对框架不可用,无论其脚本请求什么。这就是你如何放置需要摄像头的视频通话小部件而不也给页面上的其他每个框架那个摄像头的方式。
sandbox,它会锁定被嵌入的页面:没有脚本、没有弹窗、没有恶意内容。然后你仅交还它实际需要的权限。就像借给某人一间房间但保留一些柜子的钥匙。 sandbox限制框架,你逐个令牌放宽它,如allow-scripts和allow-forms。独立的allow属性是授予摄像头、麦克风或全屏的东西。只给每个嵌入内容实际请求的权限。 sandbox移除功能,allow授予受限功能并默认拒绝。陷阱是在同源框架上配对allow-scripts和allow-same-origin,因为那样会让页面解锁自己的沙箱。只有在你完全信任内部内容时才这样做。 安全和性能注意事项
嵌入内容很方便,但每个框架加载一个完整的额外页面,所以一堆框架会让你的页面感觉缓慢。两个习惯让你避免麻烦:仅从你信任的网站嵌入,因为你邀请他们的页面到你的页面,并且不要添加比你需要的更多的框架。
之前的loading="lazy"属性也在这里帮助,在读者滚动到框架之前拿住框架。当它节省你真正的工作时,如视频或地图,才考虑使用嵌入,并在纯链接可以做同样工作时跳过它。
你添加的每个框架都是一个完整的页面加载:其自己的HTML、其自己的CSS,通常还有自己的一堆JavaScript,全部从另一个服务器获取。一个有三个第三方嵌入的页面实际上是四个页面同时加载,这就是为什么嵌入内容较多的页面感觉很重。loading="lazy"在非屏幕框架上是最便宜的胜利,倾向于一个嵌入而不是三个是下一个。
除了速度之外还有第二个关注点。你可以保护你自己的页面不被放在别人的框架内,这阻止了一个攻击者框架化你的网站并诱骗用户点击他们看不到的东西的伎俩。该保护在服务器上设置,高级级别涵盖了两个做到这一点的标头。
两个成本值得清晰的看一眼:框架对你的性能做什么,框架化可以对你的安全做什么。
在性能方面,第三方框架不是单个请求;它是一个**嵌套文档加载**,在你的页面内运行提供商的完整堆栈(他们的标记、样式、字体、分析和脚本)。它竞争相同的网络和主线程,其脚本在自己的上下文中运行,但在相同的设备上,所以一个沉重的嵌入内容可以延迟你自己的内容变成交互式。loading="lazy"延迟非屏幕框架,整合嵌入内容胜过调整任何单个的。在假设嵌入内容是免费的之前测量真实成本。
在安全方面,风险是**点击劫持**:攻击者在他们自己的网站上将你的页面加载到框架中,使其不可见或覆盖它,并诱骗一个已登录的用户点击你的页面上的真实控制,而不意识到它。防御是宣布谁,如果有人的话,可能框架你的页面,它通过你服务器上的响应标头设置,而不是在HTML中:
X-Frame-Options: DENY拒绝让你的页面在任何框架中加载。SAMEORIGIN仅允许你自己的网站框架化它。这是更旧、更粗糙的控制。Content-Security-Policy: frame-ancestors 'self'是现代替代品。**frame-ancestors**列出恰好哪些源可能框架化你的页面(Content-Security-Policy或CSP是一个标头,告诉浏览器允许哪些内容和行为的源)。它取代X-Frame-Options,允许一个特定的允许列表而不是一个笼统的规则,并且是在任何有登录或表单的东西上设置的。
注意方向:sandbox和allow控制你嵌入的页面,而frame-ancestors和X-Frame-Options控制谁可能嵌入你。两个方向都很重要,它们在不同的地方配置。
loading="lazy"。如果纯链接可以做同样的工作,使用链接。 loading="lazy"在非屏幕框架上是便宜的胜利,更少的嵌入胜过调整一个。还有一个值得防守的框架化风险,深度潜水用两个服务器标头覆盖它。 frame-ancestors是现代控制,X-Frame-Options是较旧的一个。记住方向,因为sandbox保护你嵌入的页面,frame-ancestors保护谁嵌入你。 
