相比于动态插入外部脚本, 动态插入外部样式表(<link rel="stylesheet">)的行为简单很多: 只要插入到当前 DOM 树时,浏览器总会异步地立即下载并应用该样式表, 被从 DOM 树移除时样式消失并立即触发重绘。

内联样式表(这里指<style>标签)除了不需要去下载之外,其他行为与外部样式表相同。 因此下文略过对内联样式表的讨论。

插入方式

鉴于浏览器的样式处理模型,即使是以innerHTML的方式插入的 <link> 也会被解析为 外部样式表资源 并立即开始下载。 所以下面两种插入方式效果上等价:

方式一

document.body.innerHTML = '<link rel="stylesheet" href="foo.css">';

方式二

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = "foo.css";
document.body.appendChild(link);

上述情形对<script>却会不一样,<script>会被解析但不会执行。 详细的讨论见在DOM中动态插入脚本

触发下载

可能你不会注意到外部样式表的下载是有条件的,浏览器以此来避免下载不必要的样式文件。 最为重要的一个条件是<link>必须与当前浏览上下文相连接, 也就是说新创建的 <link> 标签必须在插入到当前 DOM 树之后才会触发下载, 或者为已经在 DOM 树中的 <link> 改变其资源地址。 参见 WHATWG link type "stylesheet"

The appropriate times to obtain the resource are:

When the external resource link is created on a link element that is already browsing-context connected.

When the external resource link's link element becomes browsing-context connected.

例如下面的 HTML 中创建了一个<link>标签:

<html>
  <body>
   <script>
     var link = document.createElement('link');
     link.rel = 'stylesheet';
     link.href = "foo.css";
   </script>
  </body>
</html>

这时浏览器还不会去下载foo.css,只有插入到 DOM 时浏览器才会立即开始下载:

document.body.appendChild(link);

加载状态

如果需要在脚本中获取外部样式表的加载状态,可以绑定其onloadonerror事件。 为了兼容 IE 还可以绑定 onreadystatechange 事件。 需要注意的是<link>元素是没有readyState属性的(除了IE)。

document.body.appendChild(link);
link.onload = link.onerror = link.onreadystatechange = function(e) {
    console.log('loading state changed:', e.type);
};

细心的读者可能会发现上述代码在插入到 DOM 之后才绑定事件, 由于样式的下载是异步的,所以这是没有问题的。 关于资源载入时机更详细的讨论请参考: 异步渲染的下载和阻塞行为

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2017/01/16/dynamic-link-insertion.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。