【问题标题】:How can I load a stylesheet through ajax AND have a callback when the loading is done如何通过 ajax 加载样式表并在加载完成后进行回调
【发布时间】:2017-10-28 03:02:55
【问题描述】:

我需要通过ajax加载一堆CSS文件,并在样式表加载完成后调用动画,否则动画会失败。

我已经这样做并且过去工作得很好,直到我遇到这个跨域是这样的:

$.get(resource.url, {cache:true}, function(css) {
    //Now that the stylesheet is in the browser cache, it will load instantly:  

    $("head").append($("<link>",{
       rel: "stylesheet",
       type: "text/css",
       href: resource.url
    }));

}).then(function(){
   //Animation here that depends on the loaded css
});

只要resource.url 在同一个域中,它就可以正常工作。一旦我尝试从另一个域 $.get 加载 css,就会像这样失败:

XMLHttpRequest cannot load https://example.com/style.css. Origin https://example.com is not allowed by Access-Control-Allow-Origin.

所以我尝试通过.htaccess将CORS添加到标头中:

<IfModule mod_headers.c>
    #cross domain access is okay for resources (#107)
    <FilesMatch "\.(css|js)$"> 
        Header add Access-Control-Allow-Origin "*"
    </FilesMatch>
</IfModule>

这会将 CORS 标头添加到所有 CSS 和 JS 资源中。

由于某种原因,CORS 似乎对 chrome 或 firefox(最新版本)都没有影响。

我还注意到,在为 js 文件执行 $.getScript 时,没有强制执行相同的域策略,但它适用于 $.get:

$.get("https://example.com/script.js", {cache: false}, $.noop, "script"); 

//works regardless of CORS

但是:

$.get("https://example.com/script.js", {cache: false}, $.noop); 

//does not work (cross domain policy violation)

因此,由于 CORS 并未得到广泛支持,甚至似乎无法解决现代浏览器的问题,因此我需要类似于 $.getScript 的东西,但用于 CSS 样式表。

需要异步并有回调机制。

有什么想法吗?提前谢谢...

【问题讨论】:

  • 澄清一下:您在foo.com 上的页面正在获取其他域bar.com 上的CSS 资源。您已将 CORS 标头添加到 bar.com 上的页面,对吗?
  • @apsillers 我已将标头添加到 css 资源中。它应该在 html 页面上吗?
  • 不,我只是想确保您没有错误地在执行提取的页面/域上设置了 CORS 标头。您需要为正在获取的跨域资源设置 CORS 标头,这意味着您必须控制该跨域资源。 CORS 是一种表达方式,“嘿,大家,可以跨域访问此资源”,不是一种表达方式:“好吧,我的域上的页面,继续访问您想要的任何域”。你没有说清楚你想做什么,所以我想澄清一下。
  • @apsillers,很好,这就是我的想法,也是我实现它的方式。后者根本没有意义......
  • 好的,太好了。现在写一个答案!

标签: javascript jquery css ajax cors


【解决方案1】:

$.get 使用 Ajax,必须遵守同源策略。

$.getScript 通常使用 Ajax,但它也有一个仅用于跨域使用的后备选项。通过&lt;script&gt; 标签导入的脚本不受同源规则的约束,因此 jQuery 只需在页面中添加一个&lt;script&gt; 标签并将其src 属性设置为请求的脚本 URL。

对于通过 &lt;link&gt; 标签加载的 CSS 资源,也有同样的豁免。在一个完美的世界中,您应该能够 1) 创建一个新的 &lt;link&gt; 元素,2) 将其 href 属性设置为正确的 URL,以及 3) 在该元素上侦听 load 事件。我从 jQuery 论坛添加了对 this solution 的回调:

// note: non-compatible example code, see below for better code
jQuery.getCSS = function( url, media, callback ) {
    jQuery( document.createElement('link') ).attr({
        href: url,
        media: media || 'screen',
        type: 'text/css',
        rel: 'stylesheet'
    }).appendTo('head')
    .on("load", callback);
};

这里有个小问题:它不能跨浏览器工作。 &lt;link&gt; 标记不会在所有浏览器中触发 load 事件 - 请参阅 jQuery bug report 了解可能的 $.getCSS 函数。

所以,一般情况下workaround is a bit insane:添加 CSS URL 作为新 &lt;img&gt; 标记的源并监听其 onerror 处理程序:

// correct code!
jQuery.getCSS = function( url, media, callback ) {
    jQuery( document.createElement('link') ).attr({
        href: url,
        media: media || 'screen',
        type: 'text/css',
        rel: 'stylesheet'
    }).appendTo('head');

    jQuery( document.createElement('img') ).attr('src', url)
    .on("error", callback);
};

您还可以添加一些优化并在 &lt;link&gt; 上监听 load 事件(对于支持它的浏览器)和在 &lt;img&gt; 上的 error 事件(对于那些没有),并放置一些逻辑以确保回调只被调用一次,以防两个事件都发生。

至于让$.getCSS返回一个延迟对象,这有点超出我的专业领域,但没有理论上的理由不能做到。

【讨论】:

  • 嘿,非常感谢,这应该可以。返回一个承诺对象不应该是这个问题。但是,它会访问服务器两次,不是吗?
  • 是的,确实如此,但遗憾的是,我们必须为在我们的 &lt;link&gt; 元素上没有 load 事件付出代价。但是,如果您的浏览器/服务器对缓存很聪明,它应该只下载 CSS 文件的内容一次;第二个请求应该返回 304 Not Modified 消息。
  • 另一个不可推广的解决方案(并且需要 CSS3)是在您的 CSS 文件中包含一个动画,该动画适用于您页面中的隐藏元素。监听该隐藏元素上的animationstart 事件,您将知道 CSS 文件已加载。同样,此解决方案需要样式表和页面本身之间有计划的合作,将任何 CSS 拉入任何页面并不是一个好的通用解决方案。
  • +1 现在绝对值得一票。我会暂时搁置一下,看看是否还有其他想法/反对意见。
  • 好的,它在ff中工作,并根据firebug的网络面板正确缓存。 getfirebug.com/wiki/index.php/Net_Panel
【解决方案2】:

使用延迟而不是创建 jQuery 插件,将我自己的 apsillers 答案的实现放在一起。

fetchStyleSheet(url, media = 'screen') {
    let $dfd = $.Deferred(),
        finish = () => $dfd.resolve(),
        $link = $(document.createElement('link'))
            .attr({
                media,
                type: 'text/css',
                rel: 'stylesheet'
            })
            .on('load', 'error', finish)
            .appendTo('head'),
        $img = $(document.createElement('img'))
            .on('error', finish); // Support browsers that don't fire events on link elements

    $link[0].href = $img[0].src = url;
    return $dfd.promise();
}

fetchStyleSheet('/url/to/my/stylesheet.css').done(() => {
    // Stuff after the sheet has loaded
});

这个话题已经有好几年了,我不知道 img 上的备份事件是否仍然需要,但实现起来似乎相当简单。

对于我的特定用例,我实际上并不关心工作表是否无法加载,因此这两种情况都得到了解决。如果您想单独处理这些案例,您可以将参数传递给 finish 处理程序以适当地解决/拒绝。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-30
    • 1970-01-01
    • 2011-07-25
    • 2016-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多