filamentgroup / loadcss Goto Github PK
View Code? Open in Web Editor NEWLoad CSS asynchronously
License: MIT License
Load CSS asynchronously
License: MIT License
Doesn't render loadCSS when return the previous page on iOS Safari. I think the problem is occurred for browser cache. Can I get to prevent the page from browser cache?
If we can append after the last stylesheet, even if it's far after it, that's probably a good fallback, and potentially a fine default for all browsers if we want to just do that. For example:
var scope = document.body || document.getElementsByTagName( "head" )[ 0 ];
var lastElement = scope.childNodes[ scope.childNodes.length - 1 ];
...gives us an insertion point that's as far down in the DOM as can be, and certain to be after the last script/style/link in the indexed dom at least.
Unfourtunately this script doesn't solve this.
You load CSS with script tags either using you CSS loader - the result would be the same
And, Oh the trick with media attribute is quite old.
So, looking forward for having a solution to google page speed annoying message
What if offer already minified code snipped ready for copy-paste to html file?
Thanks!
It's really quite simple, I think you could do more if you wish, like:
solve ie imposes a maximum limit of 32 individual styleSheet.
I like the loadCSS approach, currently I've included loadCSS manually . Is there any change you can add a bower.json to the repository so I can use Bower to install it?
In the following demo page I've referenced a slow-loading CSS file with link[rel=preload]
and polyfilled its request using loadCSS
if a feature test for preload
fails. _(Note: the slow-responding CSS file is not critical to this test, but it's useful for visually observing whether or not the CSS request blocks rendering across our test pages).
It works, but I'm unsure whether the syntax and logic lines up with how this feature is intended to work.
Hello and thank you for your script! I implemented it because Google PageSpeed recommended me to remove/optimize blocking CSS files. But it seems that Google PageSpeed does not care about the asyncronous load. With your script implemented, PageSpeed still recognizes the CSS file as blocking.
Any idea how this can be solved?
Hello @scottjehl
Is there a good solution to go with loadCSS in combination with the power that comes with localStorage.
The way I want to go down is like this:
That would be much better than any cookie-based solution anyways but the main advantage I see is, that apart from loading CSS asynchronously, we could prevent the script from firing everytime a user is requesting a page in a website.
My questions:
What do you think?
How could one build that (I'm too bad at writing JS, otherwise I would have pasted a possible solution with my question)?
May be that has been thought of already and I just couldn't see it?
Hey Scott,
Can you tell me what the current browser-support of this library is? I couldn't find any documentation on that.
Either way, some pretty good stuff in here!
thanks - Ruben
If the CSS href
begins with ../
, the indexOf
test never returns true, the media
attribute keeps the only x
value, and the setTimeout
goes indefinitely.
This is because the browser translates the relative path to an absolute one, that doesn't contain the ../
part anymore.
Can the docs be updated to specify what's the advantage of this are over say...
<!DOCTYPE html>
<meta charset="utf-8"/>
<title></title>
<style type="text/css">
/* inlined critical css styles:
- window dressing
- main components
etc */
</style>
<body>
<!-- html content from server for initial render -->
<style type="text/css">
@import '/the/rest/of/the.css';
</style>
<script async
src="your/javascript.js"
></script>
Chrome Version 40.0.2214.111 (64-bit)
This doesn't show up in Firefox or Safari. Occurs in my local dev setup and on your demo site.
Thanks for all the great tools, including this one. I know user experience is more important than PageSpeed Insights score, and this is not a PageSpeed troubleshooting forum, however I'd like to check that I'm not doing something wrong with loadCSS before blaming Pagespeed.
Here's the entirety of my code, which is located in the <head>
:
<script>
function loadCSS(a,b,c){
"use strict";function g(){for(var b,e=0;e<f.length;e++)f[e].href&&f[e].href.indexOf(a)>-1&&(b=!0);b?d.media=c||"all":setTimeout(g)}var d=window.document.createElement("link"),e=b||window.document.getElementsByTagName("script")[0],f=window.document.styleSheets;return d.rel="stylesheet",d.href=a,d.media="only x",e.parentNode.insertBefore(d,e),g(),d
}
loadCSS("/public/fontface-1f24f01.css");
</script>
<script>
loadCSS("/public/bootstrap-ebf66f6.css");
loadCSS("/public/global-e4c2321.css");
loadCSS("/public/condo-01c4f25.css");
</script>
I then also have critical CSS inlined in the <head>
, and the user experience is great (i.e. works as expected and promised by @scottjehl!). However, PageSpeed Insights reports:
Your page has 4 blocking CSS resources. This causes a delay in rendering your page.
And then lists those four .css files from above.
I'm satisfied that user experience is vastly improved but disappointed if this is a "false positive" from PageSpeed. Any suggestions?
loadCSS("../dist/css/style.css");
If I'm calling loadCSS this way, it does not work. The problem is IMHO on line 29.
loadCSS does not found relatively called stylesheet and media
attribute is remaining on only x
value.
On my project I can fix it easily, but maybe someone will experience the same problem.
I think we should consider delaying the link
append until a time when we can be sure that we're seeing all the scripts and styles in the DOM.
Currently, loadCSS runs before the DOM is ready, and depending on its location in the DOM, it'll often only see nodes that come before its parent script tag. That's often not a problem, particularly if you place the script tag that contains loadCSS after your other stylesheets. But if you don't, it'll often mean that the new link will be inserted before later stylesheets, possibly messing with cascade.
waiting for domcontentloaded or RAF is probably best. If not that, maybe waiting for document.body would at least be sufficient to get all the links in head
I'm using loadCSS
on SVGOMG. However, it doesn't appear to work in IE11 - initial render is blocked until CSS downloads.
I tried adding ss.lazyLoad = 1
, but no change.
In the end I moved the loadCSS
call after my content jakearchibald/svgomg@d966d83, and that did the trick
I'm currently using this on a project, and i've noticed that on IE9 the site just freezes, it doesn't load the css. I've added ss.type = "text/css"; figured that could be the solution but it didn't work.
Depending on the size of one's CSS, this could exacerbate the FOUT, but I'd love to see some metrics on non-JS solutions, or at least solutions not initially requiring JS.
For example, how much perf cost could one save by:
@scottjehl's comments so far:
You could! JS can make the async request sooner tho. We also set a cookie once we request it (so we can assume it's in cache)
we typically have a link to the full css in a noscript, but the JS probably could reuse that reference, I guess
we tend to define our urls in meta tags now, but there's an overlap there in the fallback css link, if that's what you mean
The old onload
callback worked fine (it's called when the styles are loaded and applied). However the new onloadCSS()
callback fires too early.
E.g. loading a stylesheet with #foo { display: none; }
and asserting getComputedStyle(document.getElementById('foo')).display === 'none';
fails with the onloadCSS()
callback, but passes with the onload
callback.
After some trial and error, I determined that this is caused by the ss.media = "only x";
trick. While loadCSS's internal callback that changes the media value does run first, the second instance of the poller from onloadCSS() invokes the callback at a time that the styles are not yet live.
Removing the ss.media
hack and its first callback counterpart fixes the issue.
Is it currently possible (or easy to implement a feature that does so) to listen for a success callback function when the stylesheet has loaded successfully? Eg...
loadCSS("stylesheet.css", null, null, function(status) {
if (status === true) {
console.log("Stylesheet injected.");
} elseif (status === false) {
console.log("Timeout or unsupported browser.");
}
});
I feel like this could be really useful, what do you guys think? Can you already do this?
I'm curious how loadCSS stacks up against Google's documented CSS delivery mechanism.
The code in question below:
<script>
var cb = function() {
var l = document.createElement('link'); l.rel = 'stylesheet';
l.href = 'small.css';
var h = document.getElementsByTagName('head')[0]; h.parentNode.insertBefore(l, h);
};
var raf = requestAnimationFrame || mozRequestAnimationFrame ||
webkitRequestAnimationFrame || msRequestAnimationFrame;
if (raf) raf(cb);
else window.addEventListener('load', cb);
</script>
Hi, what's the best way to make it work in IE 7, 6?
Thanks.
I noticed this when testing over 3g. Sometimes, our timeout is fast enough to toggle the link
's media
attribute to all
before its stylesheet request goes out, and it ends up blocking render. Not good.
One workaround I've come up with so far involves polling the document.styleSheets
array until it increments, which I've found is a safe time to toggle the media
attribute on any connection speed, as the request appears to be in flight at the point it joins that array (yet it's still long before onload
, which is good for minimizing the times this logic would have to run). That change looks something like this:
/*!
loadCSS: load a CSS file asynchronously.
[c]2014 @scottjehl, Filament Group, Inc.
Licensed MIT
*/
function loadCSS( href, before, media ){
"use strict";
// Arguments explained:
// `href` is the URL for your CSS file.
// `before` optionally defines the element we'll use as a reference for injecting our <link>
// By default, `before` uses the first <script> element in the page.
// However, since the order in which stylesheets are referenced matters, you might need a more specific location in your document.
// If so, pass a different reference element to the `before` argument and it'll insert before that instead
// note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/
var ss = window.document.createElement( "link" );
var ref = before || window.document.getElementsByTagName( "script" )[ 0 ];
var ssLength = window.document.styleSheets.length;
ss.rel = "stylesheet";
ss.href = href;
// temporarily, set media to something non-matching to ensure it'll fetch without blocking render
ss.media = "only x";
// inject link
ref.parentNode.insertBefore( ss, ref );
// This function sets the link's media back to `all` so that the stylesheet applies once it loads
// It is designed to loop until document.styleSheets includes
function toggleMedia(){
if( window.document.styleSheets.length > ssLength ){
ss.media = media || "all";
}
else {
setTimeout( toggleMedia );
}
}
toggleMedia();
return ss;
}
This works generally, but a downside to it is it may not work well with multiple loadCSS
calls, since they'll all toggle their media as soon as the first call goes out. I think we might be able to work around this by queueing the media toggles in the order they're called. Maybe a loadCSS.locked
property would give us that sort of mechanism. The queue wouldn't incur much delay, since it's not about waiting for requests to return so much as ensuring the stylesheets have joined the CSSOM.
Since it's high-priority, we'll try and close this one out in the morning.
How do you feel about passing in the optional arguments as an options object.
loadCSS('path/to/styles.css', {
'before': document.getElementsByTagName('script')[0],
'media': 'all',
'callback': function() {
// magic
}
}
It's a little cleaner when, for example, you only want to use a callback and not toch the before
and after
arguments.
Is this the intended behavior? I noticed that in chrome you get multiple network activity for the same stylesheet where only the last retrieves the stylesheet. I am guessing that this is intended and the "multiple tries" are how the asynchronicity is achieved.
I believe for this very same reason, firefox is calling the callback function multiple times.
Any insight?
First calls goes normally on insertBefore. But when the function sets media
attribute to all
, it triggers another call for the CSS file.
Why could that be happening?
Browser: Chrome 44
OS: Max OSX 10.9.2
This would mainly mean that the default injection location would be after the last childnode element in either the body, or the head if the body isn't yet defined. We currently inject after the last style/script/link in qsa browsers, but this seems like it may be an unnecessary refinement compared to the fallback.
Lines in question: https://github.com/filamentgroup/loadCSS/blob/master/loadCSS.js#L23-L28
Sorry, noob question. Wouldn't this make the website unusable and ugly until the css has fully loaded?
This script is designed to be used inline in the head
of a page. Is there a good reason to keep all the window.
references?
Thanks for providing this excellent lib to load css async.
I pulled the latest version ( 0.1.7 , as of writing this ) from bower.
It contains the 2 .js files ( the unminified ones ).
I did this locally: ( using https://github.com/mishoo/UglifyJS2 )
uglifyjs --compress -- bower_components/loadcss/onloadCSS.js bower_components/loadcss/loadCSS.js > loadCSS.min.js
to start using the same.
It would be nice if this is present in bower repo as well, dist as well. Thanks, again.
Hey
Should there be a <noscript>
tag included in the example for when the browser throws an error or js is simply disabled?
Just checkin'...
It worked for most part but it did not work with http://andreruffert.github.io/rangeslider.js/. I tried the callback solution but it still didn't work.
Maybe it has to do with callback fire too early, hard for me to say.
It would be nice if you could specify a value for the integrity
attribute of the link, to enforce subresource integrity.
Hey,
Great job with this script. This is not an issue with the code. It's more of a tweak. To make your great idea more useful to more people.
Can you provide some more examples for the non so technical people out there? For example how to setup loadCSS for 5 CSS?
I can help with writing the wiki page also.
Firefox triggers an onload callback twice when the media attribute is changed before load succeeds.
This does not happen when the ss.media = media || "all";
line is commented out. It does happen even if the media value doesn’t change (for example changing the initial ss.media = "only x";
to ss.media = "all";
)
This is probably something we should file upstream at Mozilla’s tracker.
Update: also happens in newest Chrome, Safari 8.0.3 and I’m assuming others.
Test url: http://filamentgroup.github.io/loadCSS/test-callback.html
Your README.md https://github.com/filamentgroup/loadCSS/blob/master/README.md#optional-arguments says:
"By default, your stylesheet will be inserted before the first script tag in the DOM (which may be the one shown above)."
The file loadCSS.js https://github.com/filamentgroup/loadCSS/blob/master/loadCSS.js#L13 says:
"By default, loadCSS attempts to inject the link after the last stylesheet or script in the DOM."
So which one is it?
Hey guys about the line #65 of the README it says FOIT,
What that means (FOIT) ?
https://github.com/filamentgroup/loadCSS/blame/master/README.md#L65
Reproduced with 250KB locally hosted stylesheet, network connection throttled in Chrome to EDGE.
Some discussion for this took place in #41, but we this needed to be its own ticket.
Hey, fantastic piece of script here! Just used it and it worked well (even tested it in IE11). I am just curious on how I would go about loading multiple CSS files using this piece of script. Is there any documentation available to support this?
I understand this code works best for inline and defer. The question is should the inline style go above your javascript or below it?
So I have a test branch with async-loaded css using loadCSS, and everything seems to be working as expected (i'm even loading fonts as @scottjehl suggests in this article). On first visit, only 'above the fold' css is rendered and the rest is loaded in async. Great. However, even when the css file is cached, there's a small blip of time from when the DOM is first rendered to when the cached css file is parsed and selectors are matched. So if, for example, I scroll to the bottom of the page and the refresh, the browser will keep me at the bottom of the page, and I'll see a blip of unstyled content.
Does that make sense? What do you do to combat this? One thing that comes to mind would be setting a cookie as Scott did in that article for the fonts and writing (blocking with .writeIn or something) a stylesheet element for those who presumably have the file cached. Another option would be to include the entire page's css inline (this sounds like an awful idea, but I'm actually only loading css async for the homepage, and assuming all other pages will have the css file cached after that, so it wouldn't be the worst thing).
Here's a screenshot of the network timeline, where the first entry is the document and the second is the cached css file, with a gap not only between when the first one ends and the second begins, but we obviously won't actually see the styled DOM until the second one ends.
Thank you so much for any insight!
Browsers are built to optimize appending new styles to the end. That's why when you apply an inline style, browsers don't have to recompute the styles of the world in order to apply it.
The model is built to cheaply apply additive style rules; the browser takes the current CSSOM (basically) and applies the new css on top of it. It's cool.
Because CSS order matters, if new CSS is added in the middle (let's say halfway down), then the browser invalidates the style computation of 50% of the styles and has to recompute due to the potential cascade changes.
loadCSS injects the script like so:
var ref = before || window.document.getElementsByTagName( "script" )[ 0 ];
ref.parentNode.insertBefore( ss, ref );
This injection is better than adding to the very top of the head, but there's a good chance it is before other styles.
In discussions with Flipkart, the performance disadvantage they encountered with this was significant. I would expect similar results amongst publishers with plenty of DOM, the folks that typically would be eager to use loadCSS.
I'd recommend placing the injected sheet at the end of all recognized styles. (Bonus: subsequent loadCSS calls will be generally placed after eachother, which is probably what folks would expect)
So I'd go for something like this:
var elems = document.querySelectorAll('style,link[rel=stylesheet]');
var ref = elems.item(elems.length - 1);
ref.parentNode.insertBefore(ss, ref.nextSibling);
It finds all external stylesheets and inline styles, gets the last one (in DOM order), then adds our ss
right after it.
hows that sound?
Was doing some testing around ownerNode
on #40 and it looks like http://filamentgroup.github.io/loadCSS/test.html does block render in IE8.
Works fine in IE9.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.