Last active
April 12, 2016 12:22
-
-
Save ZauberNerd/9476304 to your computer and use it in GitHub Desktop.
Generates CSS for all elements currently in the viewport (Above The Fold) (unstable and hacky, needs more refactoring).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
'use strict'; | |
var CSSCriticalPath = function (w, d) { | |
var css = {}; | |
var findMatchingRules = function (node, pseudo) { | |
var rules = w.getMatchedCSSRules(node, pseudo); | |
var duplicate = false; | |
var rulesArr = null; | |
var rule = null; | |
if (rules) { | |
// find all non-duplicate rules matching the current node | |
// and push them in an array, using it's selector string as key. | |
for (var i = 0, l = rules.length; i < l; i += 1) { | |
rule = rules[i]; | |
rulesArr = css[rule.selectorText] || []; | |
duplicate = rulesArr.some(_exists.bind(null, rule.cssText)); | |
if (!duplicate) { | |
rulesArr.push(rule); | |
} | |
css[rule.selectorText] = rulesArr; | |
} | |
} | |
}; | |
var parseTree = function () { | |
// Get a list of all the elements in the view. | |
var height = w.innerHeight; | |
var el = d.documentElement; | |
var NFShow = NodeFilter.SHOW_ELEMENT; | |
var walker = d.createTreeWalker(el, NFShow, _accept, true); | |
do { | |
var node = walker.currentNode; | |
var rect = node.getBoundingClientRect(); | |
if(rect.top < height) { | |
// element is in the first scroll of the screen | |
// now go and find every rule which matches the current | |
// element or it's pseudo before/after elements | |
findMatchingRules(node); | |
findMatchingRules(node, ':before'); | |
findMatchingRules(node, ':after'); | |
} | |
} while (walker.nextNode()); | |
}; | |
this.generateCSS = function () { | |
var ret = ''; | |
for (var k in css) { | |
ret += _mergeRules(css[k]) + '\n'; | |
} | |
return ret; | |
}; | |
this.getSelectors = function () { | |
var selectors = []; | |
for (var k in css) { selectors.push(k); } | |
return selectors; | |
}; | |
parseTree(); | |
}; | |
function _exists(text, r) { return text === r.cssText; } | |
function _accept() { return NodeFilter.FILTER_ACCEPT; } | |
function _xhr(url, callback) { | |
var xhr = new XMLHttpRequest(); | |
xhr.open('GET', url, true); | |
xhr.onreadystatechange = function () { | |
var isSuccess = this.status >= 200 && | |
this.status < 300 || this.status === 304; | |
if (this.readyState === 4) { | |
if (isSuccess) { | |
callback(null, this.responseText); | |
} else { | |
callback(this, null); | |
} | |
} | |
}; | |
xhr.send(); | |
} | |
function _mergeRules(rules) { | |
// this function is called once for every distinct selector string | |
// it takes the first rule from the array, walks through all other | |
// rules and sets their properties on the first rule to finally return | |
// the first rule. | |
var firstRule = rules[0]; | |
for (var i = 1, l = rules.length; i < l; i += 1) { | |
for (var j = 0, k = rules[i].style.length; j < k; j += 1) { | |
var key = rules[i].style[j]; | |
var value = rules[i].style.getPropertyValue(key); | |
// weird: sometimes the value of 'content' isn't quoted. | |
var isQuoted = value.indexOf('"') > -1 || | |
value.indexOf('\'') > -1; | |
if (key === 'content' && !isQuoted) { | |
value = String('"' + value + '"'); | |
} | |
firstRule.style.setProperty(key, value); | |
} | |
} | |
return firstRule.cssText; | |
} | |
function _getStyleSheets() { | |
// finds all stylesheets on a page and returns them as an array | |
var links = document.getElementsByTagName('link'); | |
var styleSheets = []; | |
for (var i = 0, l = links.length; i < l; i += 1) { | |
if (links[i].rel === 'stylesheet') { | |
styleSheets.push(links[i]); | |
} | |
} | |
return styleSheets; | |
} | |
function removeStyleSheets(links) { | |
// removes all stylesheets from the document which are provided as an array. | |
for (var i = 0, l = links.length; i < l; i += 1) { | |
links[i].parentNode.removeChild(links[i]); | |
} | |
} | |
function _loadStyleSheets(links, callback) { | |
// asynchronously load stylesheets and maintain their order | |
var finished = 0; | |
function _rcv(url, err, responseText) { | |
for (var i = 0, l = links.length; i < l; i += 1) { | |
if (links[i].href === url) { | |
links.splice(i, 1, responseText); | |
} | |
} | |
if (++finished === links.length) { callback(links.join('\n')); } | |
} | |
for (var i = 0, l = links.length; i < l; i += 1) { | |
_xhr(links[i].href, _rcv.bind(null, links[i].href)); | |
} | |
} | |
function init(links, callback) { | |
// can be called with an array of objects with a 'href' property set | |
// if stylesheets on the page are not available via xhr (cross-domain-foo) | |
if (typeof links === 'function') { | |
callback = links; | |
links = undefined; | |
} | |
var styleSheets = links || _getStyleSheets(); | |
_loadStyleSheets(styleSheets, function (text) { | |
var el = document.createElement('style'); | |
el.innerHTML = text; | |
document.body.appendChild(el); | |
var cp = new CSSCriticalPath(window, document); | |
callback(cp, el); | |
}); | |
} | |
init([{ href: '/build/main.css' }], function (cp, styleEl) { | |
if (typeof window.callPhantom === 'function') { | |
window.callPhantom(cp.getSelectors()); | |
} else { | |
// replace the original stylesheets with the generated version | |
// (to show the differences) and copy the contents of the generated | |
// stylesheet in the clipboard if ctrl+c is pressed on the document | |
// contex. | |
var css = cp.generateCSS(); | |
removeStyleSheets(_getStyleSheets()); | |
styleEl.innerHTML = css; | |
var out = document.createElement('textarea'); | |
out.innerHTML = css; | |
out.style.cssText = 'position: absolute; top: 0; left: 0;' + | |
'width: 0; height: 0;'; | |
document.body.appendChild(out); | |
document.addEventListener('keydown', function (ev) { | |
if (ev.ctrlKey) { | |
out.focus(); | |
out.select(); | |
} | |
}, false); | |
} | |
}); | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment