如何在不使用内联脚本的情况下异步加载CSS(符合CSP)

问题描述 投票:0回答:6

我想实现异步加载 CSS 文件以获得更快的性能。不过我也想要安全性,所以我希望我的网站有 CSP。

<link rel="stylesheet" media="print" class="AOcssLoad" .... onload="this.onload=null;this.media='all';" />

无需详细说明,它希望我避免像

onload
和许多其他属于元素一部分的 JS。

我希望它看起来像这样

<link rel="stylesheet" media="print" class="AOcssLoad" href="" />

请建议一种无需上面使用的内联 JS 即可实现异步 CSS 文件的方法。

我们可以使用内联

<script>
标签或单独的 JS 文件。

我尝试了以下代码作为内联 JS。下面是 JS 的 HTML,

<script  nonce="" type="text/javascript" data-exclude="true">

var Script = document.getElementsByClassName("AOcssLoad");
for (var i = 0 ; i < Script.length; i++) {
    this.className += " Loading";
    Script[i].addEventListener("load", function({
        this.onload=null;this.media="all";
        this.className += " OnLoad";
    });
}
</script>

虽然它有效,但非常不可靠。

我无法理解这个问题,但我想说它只在 50% 的情况下有效,有时只需重新加载页面就可以解决/打破问题,而对 css/html/cache 没有明显的更改。

请帮助我改进这一点,或者为此建立更好的方法。

编辑:

按照评论中的建议,我尝试了不同的方法,包括 GitHub 上其他资源的链接。

这些方法并不可靠,我想说它们的成功率不到 50%。 不过,我尝试对所有 css 文件使用

jQuery(document).ready()
add media="all"
,但这会增加 TBT(总阻塞时间),从而影响我的网站性能

编辑2:

正如你们中的许多人在答案中反复指出的那样,使用 DOMcontentLoaded 和许多其他方法可以帮助完成我想要实现的事情。

然而,这些方法都会导致TBT(总阻塞时间)的显着增加。

如果有一种不损害 TBT 的方法,我们将不胜感激。

javascript html css asynchronous content-security-policy
6个回答
1
投票

我建议使用

fetch().then()
并将其作为
style
元素注入:

var stylesheetURLS = ["style.css", "style2.css"]
stylesheetURLS.forEach(url => {
  fetch(url).then(response => response.text()).then(stylesheet => {
    var styleElement = document.createElement("style");
    styleElement.textContent = stylesheet;
    document.head.append(styleElement);
  });
});

我不确定

fetch
是否很慢,但如果是的话我也不会感到惊讶。


替代

fetch
XMLHttpRequest

var stylesheetURLS = ["style.css", "style2.css"];
stylesheetURLS.forEach(url => {
  var request = new XMLHttpRequest();
  request.open("GET", url);
  request.send();
  request.onload = function() {
    var styleElement = document.createElement("style");
    styleElement.textContent = request.responseText || request.response;
    document.head.append(styleElement);
  }
});

我再次不确定这是否比

fetch

更快

1
投票

我测试过下载一个 css 文件,下载时间超过 6 秒,我可以确认下载不会对 TBT 产生影响。正如谷歌所说,TBT 是浏览器无法用于用户输入的时间,例如当用户单击按钮或滚动时,浏览器将不会做出反应。较长的 TBT 通常是由于主线程繁忙而导致的,因为它有太多工作要做。我认为处理 CSS(将所有规则应用于 html)是您的 TBT 增加的原因,因为下载文件已经在后台完成(异步),并且不会成为较长 TBT 时间的原因。

下面是下载大文件时TBT不增加的示例:

待定: enter image description here

如您所见,下载时间超过 6 秒,但还没有达到 TBT: enter image description here


0
投票

您可以使用基于 jQuery 的vanilla JS 函数

.ready()
:

document.addEventListener("DOMContentLoaded", () => {
  document.querySelectorAll(".AOcssLoad").forEach(el => {
    el.media = "all"
    console.log(`Loaded: ${el.href}`)
  })
});
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-utilities.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-reboot.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-grid.min.css" />
<h1>Hello world!</h1>

但是,无论您选择哪种解决方案,请注意您都必须处理无样式内容的 Flash (FOUC)。考虑管理样式表文件的其他方法


0
投票

为什么不注射它

<script>
document.write("<link rel=\"stylesheet\" media=\"print\" class=\"AOcssLoad" href=\"\" />
");
</script>

并将

<script>
标签放置在您想要放置
<link>
标签的位置的正上方。

或者只是

<link rel="stylesheet" media="print" class="AOcssLoad" href="" />

除了 css 将异步加载并且您可以使用 csp 之外,什么都没有发生


0
投票

你的脚本就是错误的,而且它根本无法工作,甚至 50% 的情况下都无法工作。

var Script = document.getElementsByClassName("AOcssLoad");
for (var i = 0 ; i < Script.length; i++) {
    this.className += " Loading"; // here `this` is `window`
    Script[i].addEventListener("load", function({ // <-- this is an Object
        this.onload=null;this.media="all"; // <-- this is a syntax error
        this.className += " OnLoad";
    });
}

这是您可能想要编写的内容的重写,其中还包括检查链接是否在脚本运行之前加载,以防万一(例如缓存)。

const links = document.getElementsByClassName("AOcssLoad");
for (const link of links) {
  link.className += " Loading";
  if(link.sheet) { // "already loaded"
    oncssloaded.call(link);
  }
  else {
    link.addEventListener("load", oncssloaded, { once: true });
  }
}
function oncssloaded() {
  this.media = "all";
  this.className += " OnLoad";
}
<link rel="stylesheet" media="print" class="AOcssLoad" href="data:text/css,body{color:green}" />
Some green text


0
投票

我知道这个帖子已经很老了,但我会把它扔掉,因为这是我目前在 WordPress 中使用的东西......

首先,使用标准 WordPress 工具添加样式表 (

wp_enqueue_style()
)。

然后:

add_filter('style_loader_tag', 'optimize_async_style',10,4);
function optimize_async_style(string $tag, string $handle, string $src, string $media): string
{
    $nonce = wp_create_nonce();
    $nonce = " nonce='{$nonce}'" : "";
    $tag = "<link rel='preload' id='{$handle}-css' href='{$src}' as='style' media='{$media}'{$nonce}>\n".
            "<script{$nonce}>document.getElementById('{$handle}-css').addEventListener(".
                "'load',(e)=>{e.currentTarget.rel='stylesheet';},{once:true});".
            "</script><noscript>" . trim($tag) . "</noscript>\n";

    return $tag;
}

style_loader_tag
过滤器让我们可以用
link
更改
rel='preload'
标签,后跟
script
标签,加载后将其更改为
rel='stylesheet'
。这提供了样式表的异步加载。另请注意,我们为 CSP 的
link
script
标签分配了一个随机数。

输出应类似于:

<link rel='preload' id='AOcssLoad-1' href='...' as='style' media='all' nonce='4783a68661'>
<script nonce='4783a68661'>document.getElementById('AOcssLoad-1').addEventListener('load',(e)=>{e.currentTarget.rel='stylesheet';},{once:true});</script>
<noscript><link rel='stylesheet' id='AOcssLoad-1' href='...' media='all' /></noscript>

然而,这是使用 id 而不是 class。

© www.soinside.com 2019 - 2024. All rights reserved.