在 HTML 中包含资源的新思路

注意:这篇文章描述了一种我们仍需要测其试性能影响的实验技术。 它可能最终会成为一种有用的工具,也有可能成为不被推荐的做法。 无论哪种方式,它对我们来说很有吸引力!

只要我一直工作在 Web 上,就需要一种简单的 HTML 驱动方式,将另一个文件的内容直接包含在页面中。 例如,我经常希望向页面添加额外的 HTML,或者嵌入 SVG 文件的内容,以便我们可以为其设置动画和样式。 通常我们通过使用 JavaScript 获取文件并将其内容附加到特定元素,或者通过在服务器端去包含文件来实现这种嵌入,但在大多数情况下,这些方法都不是我们想要的。

本周我在思考如何用一些新的与 fetch 相关的标记模式来实现这一点,例如 rel="preload" 或 HTML import,但我总是得出的相同结论,即这些都不能使你方便地访问所取得的文件的内容。 然后我想,假设浏览器允许我在父文档中检索 iframe 的内容,也许一个旧的 iframe 可能是一个很不错的模式。 事实证明,它肯定会的!

一个短小的演示:包含 SVG

下面是一个内联(嵌入式)SVG 图形。它是从外部文件 signal.svg中加载的。

要加载并嵌入 SVG 文件,我用了下面的标记:

<iframe src="signal.svg" onload="this.before((this.contentDocument.body || this.contentDocument).children[0]);this.remove()"></iframe>

尽管此标记以 iframe 开头,但如果你使用开发人员工具检查上面的图形,将会看到 SVG 的图标标记,就内嵌在 HTML DOM 中,而且找不到 iframe 元素。 这是因为代码用 iframe 加载文件,并且在删除 iframe之前,用 onload 事件在 HTML 中 iframe 的位置之前注入了 iframe 里的内容。

该方法也适用于 object 元素,无论如何它通常用于引用SVG,所以我认为这特别好。 对于一个objectsrc 属性必须用 data 替代:

<object data="signal.svg" onload="this.before((this.contentDocument.body || this.contentDocument).children[0]);this.remove()"></object>

另一个演示:包含 HTML 文件

也许更有用……这是一个使用HTML而不是SVG的例子!

可以用下面的标记加载:

<iframe src="/images/includespost/htmlexample.html" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"></iframe>

一个说明这一个:你可能已经注意到,标记片段检查 contentDocument.body 或仅检查 contentDocument。这是对 HTML 和 SVG 包含进行的规范化检查。 这是必要的,因为即使 HTML 文件本身只包含一个段落元素,浏览器也会创建一个完整的 HTML 文档来包装该段落,并包含 HTML 元素、head、body等。所以该片段会试图获取 iframebody 元素(如果存在),如果不存在,它将会用于整个文档。

值得注意的是,如果你要导入包含多个元素的 HTML 文件,我建议将其全部包装在 div 中,以使 iframe 标记能够简单地查找 body中的第一个子节点。

好处

与我们过去使用的其他模式相比,这种模式有一些很明显的好处:

  • 这是声明性的。 与大多数自定义 JavaScript 方法不同,这个方法是 HTML 驱动的,它在标记中的目的非常清楚,一目了然。
  • 它适用于 HTML 或 SVG。 我不确定你想要包含什么东西,但这至少满足了我自己的需求。
  • 这是异步的! 内容加载不会阻止页面渲染,这是 iframe 的性质。
  • 它是缓存友好的。 与服务器端嵌入不同,此模式允许我们包含外部文件,同时允许自然缓存文件以供日后重用。 (使用服务器端包含的内容,在客户端缓存是可能的,但难以做到)。
  • 无论 JavaScript 是否运行,它都会显示内容,因为这就是 iframe 的设计目标。 JavaScript 可以将 iframe 的内容移动到父文档中,即便失败了,你仍会看到包含的内容。
  • 它没有留下任何痕迹:iframe 将内容导入页面后会被删除。 注意:你可能希望为 iframe 指定 border:0; 甚至可以在加载时安全地隐藏它(或许通过 onerror 事件再次显示它?)。
  • 它适用于各种浏览器:到目前为止,在我的简短测试中,它适用于 Chrome,Firefox,Safari 和 Edge。 IE 会显示 iframe 中的备选内容,但我认为可以通过调整 onload 处理中的 JS 来获得对 IE 的支持,因为它目前用的是 IE 不喜欢的语法。 稍微调整一下,我认为 IE 支持是可能的。
  • 如果你愿意的话,它甚至可以包含在一个 Web 组件中,正如 Andy Bell 巧妙地演示的那样(这是一个更清晰的标记,但就 JS 依赖性来说更脆弱一点)。

考虑其他可能的用途很有趣……也许你可以引入 HTML 模块及其相关的 CSS 链接。 或者在文档或博客文章中嵌入推文或代码。 它甚至可能用于异步加载和应用常规的 rel=stylesheet 链接,并且优先级较低,否则很难做到(注意:我没有对这个想法进行太多的测试)。

可以惰性加载吗?是的,很快!

使用 iframe 进行此模式的另一个好处是, iframe 会在进入视口时获得延迟加载的能力。 这可以用 load ="lazy" 属性来实现,该属性也适用于 img 元素。 代码看起来是这样:

<iframe src="signal.svg" loading="lazy" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"></iframe>

可能存在的问题

iframe 在 web 上很常用,但是在页面中过度使用 iframe 可能会导致性能或内存消耗问题。 例如对页面上所有图标使用可能会过重,但是对于需要进行动画和样式化的特定图标来说,它可能会很好用。 不过现在我只能做更多的测试。

还有可能存在XSS问题,但我不确定这与其他需要注意外部内容的情况有什么不同。 你仍需要做通常的安全检查,并且最好将其看作是同域技术,尽管我也不确定。

就目前而言,这种做法有希望成为之前将另一个文件直接包含在页面中方法的改进。

反馈

我们将会继续测试这种模式,如果我们发现了什么有趣的内容,会很快发布后续内容。 如果你有什么反馈或想法,请随时在 Twitter 上与我们联系。 谢谢阅读!