Create a gist now

Instantly share code, notes, and snippets.

CriticalCSS Bookmarklet and Devtool Snippet.js
(function() {
var CSSCriticalPath = function(w, d, opts) {
var opt = opts || {};
var css = {};
var pushCSS = function(r) {
if(!!css[r.selectorText] === false) css[r.selectorText] = {};
var styles = r.style.cssText.split(/;(?![A-Za-z0-9])/);
for(var i = 0; i < styles.length; i++) {
if(!!styles[i] === false) continue;
var pair = styles[i].split(": ");
pair[0] = pair[0].trim();
pair[1] = pair[1].trim();
css[r.selectorText][pair[0]] = pair[1];
}
};
var parseTree = function() {
// Get a list of all the elements in the view.
var height = w.innerHeight;
var walker = d.createTreeWalker(d, NodeFilter.SHOW_ELEMENT, function(node) { return NodeFilter.FILTER_ACCEPT; }, true);
while(walker.nextNode()) {
var node = walker.currentNode;
var rect = node.getBoundingClientRect();
if(rect.top < height || opt.scanFullPage) {
var rules = w.getMatchedCSSRules(node);
if(!!rules) {
for(var r = 0; r < rules.length; r++) {
pushCSS(rules[r]);
}
}
}
}
};
this.generateCSS = function() {
var finalCSS = "";
for(var k in css) {
finalCSS += k + " { ";
for(var j in css[k]) {
finalCSS += j + ": " + css[k][j] + "; ";
}
finalCSS += "}\n";
}
return finalCSS;
};
parseTree();
};
var cp = new CSSCriticalPath(window, document);
var css = cp.generateCSS();
console.log(css);
})();
@PaulKinlan
Owner

Just added merging of styles.

@PaulKinlan
Owner

Fixing merge of styles.

@GlynnPhillips

Hey @PaulKinlan

Not sure if there is a reason why you have chosen to use d.body but I found that using the body as the start node missed body and html styles which could be very important on the critical css. Mind you my understanding of walkers is limited.

var walker = d.createTreeWalker(d, NodeFilter.SHOW_ELEMENT, function(node) { return NodeFilter.FILTER_ACCEPT; }, true);

instead of

var walker = d.createTreeWalker(d.body, NodeFilter.SHOW_ELEMENT, function(node) { return NodeFilter.FILTER_ACCEPT; }, true);

@PaulKinlan
Owner

@GlynnPhillips funny, I had this on my list :) great spot. not sure about head elements but let's give it ago.

@PaulKinlan
Owner

Fixed, head makes thinks look great. noticed a couple of other issues, working on them too.

@GlynnPhillips

Awesome, looking better now :)

@PaulKinlan
Owner

@GlynnPhillips I just relaised that it doesn't pull in scripts that are external to your domain (i.e cross-origin), whilst a little inconvienient I think this makes sense.

@qgustavor

I see cross-browser issues...

@GlynnPhillips

@PaulKinlan in what way? I can see script tags from external domains being 'walked' through. Is it just that it wouldn't pick up any styles which are set by these?

@mohammedzamakhan

Looks great when I tested on some websites, but it failed miserably on facebook.com AND youtube.com, it just returned bunch of selectors with css of { display: none !important; orphans: 4321 !important; }

So what is the issue here??

@marcis
marcis commented Sep 17, 2013

What about this error message: "Uncaught SyntaxError: Unexpected end of input"?

I'm using Chrome :)

@cforge79
cforge79 commented Oct 4, 2013

great work! just got pagespeed insights of 99 / 100 after moving the styles output to inline css! wow

@PaulKinlan
Owner

@cforge79 - awesome!!

@multiwebinc

Are there any workarounds for the cross origin thing? I serve all my static content from a different domain to prevent users from sending cookies to it.

@blueprintmrk

Patrick from feed the bot told me about this this is a fantastic set of code you have there. Thank you so much

@sigginet
sigginet commented Nov 7, 2013

@marcis you have to convert it to a bookmarklet (URLencoded, etc) code .. try http://chriszarate.github.io/bookmarkleter/

@luixxiul

@PaulKinlan Is it possible to pick up styles with media queries?

@satya61229

Sorry, possibly unrelated. But I want to test this.
How to use it as bookmark? I even minified it to put in bookmark in chrome but it is not taking. Possibly size!

@scunliffe

@satya61229 it should work fine minified... but don't forget you'll need to prefix it with "javascript:" ;-) (oh, and make sure you delete the comments... or the // will terminate your bookmarklet code early)

@alishahab

Hey @PaulKinlan

It should also return the pseudo-elements like ::after, ::before, hover, focus and active as well, check http://ali.shahab.pk/

@pocketjoso

A little bit of a resurrection, but I couldn't find anything on this problem:

Do you have any solution for the same-origin problem?

It seems that if you deliver your static resources via a CDN, this code will not work, because of the above problem.. I guess we can always temporarily move that CSS to be inline or so for testing, but I was hoping for something without many manual steps. :)

Awesome little tool by the way!

@pocketjoso

I built my own CriticalCSS Generator to solve these issues, something I can use for production. You can check it out here if you're interested:

https://github.com/pocketjoso/penthouse

Thanks for the inspiration!

@shidhincr

Hey @PaulKinlan @pocketjoso

Is there any solution available for CDN served css files ?

@cleberdantas

I put this script inside a CLI tool (Node) running on top of PhantomJS.

https://github.com/cleberdantas/atf

It's good for some automations.

Thanks for de original idea @PaulKinlan

@amityweb
amityweb commented May 7, 2015

Doesn't work for me in Chrome. I made it into a Bookmarklet using http://chriszarate.github.io/bookmarkleter/

Edit: Ok this gives it away console.log(css); - i can see it now :)

@amityweb
amityweb commented May 7, 2015

Doesn't grab ALL the critical CSS for us... leaves out our header menu, so we get a flash of unstyled content, mainly the header menu

@mihawk90

Just found this gem, works really well :)

When executing, I get this warning however:

'getMatchedCSSRules()' is deprecated. For more help, check https://code.google.com/p/chromium/issues/detail?id=437569#c2

@amityweb I suspect it has it has something to do with the positioning of the elements, as it works fine for me on my current project (with a fixed header nav)

@mpiontek

Not working for me :( http://neu.contur-online.de/de/index.php . After fill in only the 'critical path css', your tool gives me, the page is showing up wrong

@ctorx
ctorx commented Aug 20, 2015

It seems the getMatchCSSRules function is expecting 2 arguments. Calling it with an empty string kills the deprecated message and still works as expected.

var rules = w.getMatchedCSSRules(node, '');
@vuquangchien

It doesn't work for media query and responsive websites. I tried with Bootstrap (http://getbootstrap.com/examples/theme/). Is there any way to make it work with Bootstrap?

@james-Ballyhoo

@vuquangchien, this might be of use (I based it off Paul's code when we also hit the media queries issue) https://gist.github.com/james-Ballyhoo/04761ed2a5778c505527

@alirabet

@james-Ballyhoo I tested it, It didn't work well on default brower on Android. It works well on chrome 👍 Can you fix it for all browers?

@verkaro
verkaro commented Sep 9, 2016

non-techie reporting: this doesn't work with chromium-browser in Ubuntu 16.10 -- but running under google's chrome download worked. thanks for the work you've done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment