使用JavaScript动态加载CSS文件并带有回调函数,无需jQuery

32

我正在尝试使用JavaScript动态加载CSS文件,但不能使用其他JavaScript库(例如jQuery)。

CSS文件可以加载,但似乎无法为其获取回调。以下是我正在使用的代码:

var callbackFunc = function(){
    console.log('file loaded');     
};
var head = document.getElementsByTagName( "head" )[0];
var fileref=document.createElement("link");
    fileref.setAttribute("rel", "stylesheet");
    fileref.setAttribute("type", "text/css");
    fileref.setAttribute("href", url);

    fileref.onload  = callbackFunc;
    head.insertBefore( fileref, head.firstChild );

使用以下代码添加脚本标签以加载js文件,可以正常工作并触发回调函数:

var callbackFunc = function(){
    console.log('file loaded');     
};

var script = document.createElement("script");

script.setAttribute("src",url);
script.setAttribute("type","text/javascript");

script.onload  = callbackFunc ;

head.insertBefore( script, head.firstChild );

我这里做错了什么吗?如果有其他方法可以帮助我实现这个目标,将不胜感激。


1
我建议如果有你不想使用的库,那么在问题中不要标记它们。无论如何标记它们都会吸引[错误的]开发者来回答你的问题。 - Richard Neil Ilagan
非常类似于 https://dev59.com/YXA75IYBdhLWcg3w1sz2。无论是否使用jQuery都无关紧要(jQuery代码可以轻松转换为vanilla)。主要问题是,直到最近,WebKit不支持`link`元素的`load`事件。这是大量仍然非常普遍的移动电话,包括iOS和Android。 (不寻常的是,IE自IE7以来就支持该事件。) - Jake
7个回答

38
很不幸,在大多数现代浏览器中,没有对样式表的onload支持。但是我在谷歌上找到了一个解决方案。
引用自:http://thudjs.tumblr.com/post/637855087/stylesheet-onload-or-lack-thereof 基础知识
最基本的实现可以使用只有30行JavaScript代码的框架无关的方式完成:
function loadStyleSheet( path, fn, scope ) {
   var head = document.getElementsByTagName( 'head' )[0], // reference to document.head for appending/ removing link nodes
       link = document.createElement( 'link' );           // create the link node
   link.setAttribute( 'href', path );
   link.setAttribute( 'rel', 'stylesheet' );
   link.setAttribute( 'type', 'text/css' );

   var sheet, cssRules;
// get the correct properties to check for depending on the browser
   if ( 'sheet' in link ) {
      sheet = 'sheet'; cssRules = 'cssRules';
   }
   else {
      sheet = 'styleSheet'; cssRules = 'rules';
   }

   var interval_id = setInterval( function() {                     // start checking whether the style sheet has successfully loaded
          try {
             if ( link[sheet] && link[sheet][cssRules].length ) { // SUCCESS! our style sheet has loaded
                clearInterval( interval_id );                      // clear the counters
                clearTimeout( timeout_id );
                fn.call( scope || window, true, link );           // fire the callback with success == true
             }
          } catch( e ) {} finally {}
       }, 10 ),                                                   // how often to check if the stylesheet is loaded
       timeout_id = setTimeout( function() {       // start counting down till fail
          clearInterval( interval_id );             // clear the counters
          clearTimeout( timeout_id );
          head.removeChild( link );                // since the style sheet didn't load, remove the link node from the DOM
          fn.call( scope || window, false, link ); // fire the callback with success == false
       }, 15000 );                                 // how long to wait before failing

   head.appendChild( link );  // insert the link node into the DOM and start loading the style sheet

   return link; // return the link node;
}

这将允许您加载一个带有onload回调函数的样式表,就像这样:

loadStyleSheet( '/path/to/my/stylesheet.css', function( success, link ) {
   if ( success ) {
      // code to execute if the style sheet was loaded successfully
   }
   else {
      // code to execute if the style sheet failed to successfully
   }
} );

或者,如果您希望回调函数保持其作用域/上下文,您可以尝试类似以下的方法:

loadStyleSheet( '/path/to/my/stylesheet.css', this.onComplete, this );

这将导致IE7或更高版本自动停止脚本,因此不会触发任何回调。 - Farzin Zaker
谢谢。我也看到了另一篇博客文章,建议使用计时器来检查头部中的所有脚本,以查看所需的脚本是否已加载。文章链接在这里:http://www.phpied.com/when-is-a-stylesheet-really-loaded/。 - U.Ahmad
当我尝试执行它时,它总是返回false,尽管样式表已经成功加载。在调试时,我发现link[sheet]始终返回null。你有任何想法吗? - Tirtha
@Tirtha 看来这种方法只适用于本地 CSS,而不是跨域的(例如 jquery-ui.css 等需要备用方案)。 - AwokeKnowing
这段代码会破坏IE11,因为它包含了"finally {}"。考虑到它并没有实际作用,最好将其删除。 - Nick Steele
显示剩余4条评论

9
这种原生JS方法适用于所有现代浏览器:
let loadStyle = function(url) {
  return new Promise((resolve, reject) => {
    let link    = document.createElement('link');
    link.type   = 'text/css';
    link.rel    = 'stylesheet';
    link.onload = () => { resolve(); console.log('style has loaded'); };
    link.href   = url;

    let headScript = document.querySelector('script');
    headScript.parentNode.insertBefore(link, headScript);
  });
};

// works in IE 10, 11 and Safari/Chrome/Firefox/Edge
// add an ES6 polyfill for the Promise (or rewrite to use a callback)

1
完美运行,ES6 与我的Angular TypeScript 应用一拍即合! - Dunos
即使使用ES6 Polyfill,当问题被发布时,在iOS、Android、Chrome或Safari中都无法工作,而且在未更新补丁的系统(例如许多用户的手机)中这些浏览器仍然无法工作 - WebKit最近才添加了对link元素上的load事件的支持。 - Jake

6

我之前为此编写了一个库,它叫做Dysel,希望对你有所帮助。

Example: https://jsfiddle.net/sunrising/qk0ybtnb/

var googleFont = 'https://fonts.googleapis.com/css?family=Lobster';
var jquery = 'https://code.jquery.com/jquery.js';
var bootstrapCss = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css';
var bootstrapJs = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js';
var smokeCss = 'https://rawgit.com/alfredobarron/smoke/master/dist/css/smoke.min.css';
var smokeJs = 'https://rawgit.com/alfredobarron/smoke/master/dist/js/smoke.min.js';

// push links into an array in the correct order
var extRes = [];
extRes.push(googleFont);
extRes.push(bootstrapCss);
extRes.push(smokeCss);
extRes.push(jquery);
extRes.push(bootstrapJs);
extRes.push(smokeJs);

// let this happen
dysel({
  links: extRes,
  callback: function() {
    alert('everything is now loaded, this is awesome!');
  }, // optional
  nocache: false, // optional
  debug: false // optional
});

太棒了,又有一个库被添加到我的“必备清单”中 ;) 谢谢 - José Manuel Blasco

5
您可以在HTML文件中创建一个空的CSS链接,并为该链接设置一个ID。例如:
<link id="stylesheet_css" rel="stylesheet" type="text/css" href="css/dummy.css?"/>

然后通过 ID 名称调用它并更改 'href' 属性。


我没有访问页面的HTML的权限。我只能编辑加载第三方HTML页面的JavaScript。因此,解决方案需要基于JavaScript。但是感谢您提供的想法。 - U.Ahmad

1

yepnope.js可以加载CSS并在完成后运行回调函数。例如:

yepnope([{
  load: "styles.css",
  complete: function() {
    console.log("oooooo. shiny!");
  }
}]);

8
它会在链接元素插入时运行回调函数。 - fphilipe

1
这是我们的做法。使用“requestAnimationFrame”(如果不可用,则回退到简单的“load”事件)。
顺便说一下,这是谷歌在其“页面速度”手册中推荐的方式: https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery
<script>
    function LoadCssFile(cssPath) {
        var l = document.createElement('link'); l.rel = 'stylesheet'; l.href = cssPath;
        var h = document.getElementsByTagName('head')[0]; h.parentNode.insertBefore(l, h);
    }
    var cb = function() {
        LoadCssFile('file1.css');
        LoadCssFile('file2.css');
    };
    var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
    if (raf) raf(cb);
    else window.addEventListener('load', cb);
</script>

1
如果我理解得正确的话,这个答案与问题无关。它在页面加载时加载CSS文件。问题是如何在CSS文件加载完成后调用一个函数。 - oriadam

0

一个旧问题的新答案:

您可以使用 AJAX 请求 CSS 文件的文本,并将其放入 <style> 标签中。当样式已追加到 DOM 时,它们立即可用。

这是我想出来的脚本:

/**
 * Given a URL for a JS or CSS file, this function will
 * load the asset and return a Promise which will reject
 * on error or resolve when the asset is loaded.
 */
function loadAsset(url){
  return new Promise(async (resolve, reject)=>{
    var asset;
    if(url.trim().substr(-3).toLowerCase() === '.js'){
      asset = document.createElement('script');
      asset.addEventListener('load', resolve);
      asset.addEventListener('error', reject);
      document.head.appendChild(asset);
      asset.setAttribute('src', url);
    }else{
      var styles = await fetch(url)
        .then(c=>c.text())
        .catch(reject);
      asset = document.createElement('style');
      asset.appendChild(document.createTextNode(styles));
      document.head.appendChild(asset);
      resolve();
    }
  });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接