Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
CSS Variable Polyfill
/*
TODO:
X Maybe account for defaults: color: var(--header-color, blue);
- Verify cross domain working or not (it is working from dropbox)
- Option to wait to apply anything until all <link>s are parsed or inject what we have and update as each <link> returns
- Need to test on a more complex CSS file
- Option to save parsed file in local/session storage so there isn't a delay on additional page loads. Could only do it for links (with URLs to use as keys) and style blocks with IDs of some sort
- Need to test more complex values like rgba(255,0,0,0.5); and something with !important
- Try multiple links
- Local links
- Ajax driven site, or CSS added later the top of the stack
*/
let cssVarPoly = {
init() {
// first lets see if the browser supports CSS variables
// No version of IE supports window.CSS.supports, so if that isn't supported in the first place we know CSS variables is not supported
// Edge supports supports, so check for actual variable support
if (window.CSS && window.CSS.supports && window.CSS.supports('(--foo: red)')) {
// this browser does support variables, abort
console.log('your browser supports CSS variables, aborting and letting the native support handle things.');
return;
} else {
// edge barfs on console statements if the console is not open... lame!
console.log('no support for you! polyfill all (some of) the things!!');
document.querySelector('body').classList.add('cssvars-polyfilled');
}
cssVarPoly.ratifiedVars = {};
cssVarPoly.varsByBlock = {};
cssVarPoly.oldCSS = {};
// start things off
cssVarPoly.findCSS();
cssVarPoly.updateCSS();
},
// find all the css blocks, save off the content, and look for variables
findCSS() {
let styleBlocks = document.querySelectorAll('style:not(.inserted),link[type="text/css"]');
// we need to track the order of the style/link elements when we save off the CSS, set a counter
let counter = 1;
// loop through all CSS blocks looking for CSS variables being set
[].forEach.call(styleBlocks, function(block) {
// console.log(block.nodeName);
let theCSS;
if (block.nodeName === 'STYLE') {
// console.log("style");
theCSS = block.innerHTML;
cssVarPoly.findSetters(theCSS, counter);
} else if (block.nodeName === 'LINK') {
// console.log("link");
cssVarPoly.getLink(block.getAttribute('href'), counter, function(counter, request) {
cssVarPoly.findSetters(request.responseText, counter);
cssVarPoly.oldCSS[counter] = request.responseText;
cssVarPoly.updateCSS();
});
theCSS = '';
}
// save off the CSS to parse through again later. the value may be empty for links that are waiting for their ajax return, but this will maintain the order
cssVarPoly.oldCSS[counter] = theCSS;
counter++;
});
},
// find all the "--variable: value" matches in a provided block of CSS and add them to the master list
findSetters(theCSS, counter) {
// console.log(theCSS);
cssVarPoly.varsByBlock[counter] = theCSS.match(/(--.+:.+;)/g);
},
// run through all the CSS blocks to update the variables and then inject on the page
updateCSS() {
// first lets loop through all the variables to make sure later vars trump earlier vars
cssVarPoly.ratifySetters(cssVarPoly.varsByBlock);
// loop through the css blocks (styles and links)
for (let curCSSID in cssVarPoly.oldCSS) {
// console.log("curCSS:",oldCSS[curCSSID]);
let newCSS = cssVarPoly.replaceGetters(cssVarPoly.oldCSS[curCSSID], cssVarPoly.ratifiedVars);
// put it back into the page
// first check to see if this block exists already
if (document.querySelector('#inserted' + curCSSID)) {
// console.log("updating")
document.querySelector('#inserted' + curCSSID).innerHTML = newCSS;
} else {
// console.log("adding");
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = newCSS;
style.classList.add('inserted');
style.id = 'inserted' + curCSSID;
document.getElementsByTagName('head')[0].appendChild(style);
}
};
},
// parse a provided block of CSS looking for a provided list of variables and replace the --var-name with the correct value
replaceGetters(curCSS, varList) {
// console.log(varList);
for (let theVar in varList) {
// console.log(theVar);
// match the variable with the actual variable name
let getterRegex = new RegExp('var\\(\\s*' + theVar + '\\s*\\)', 'g');
// console.log(getterRegex);
// console.log(curCSS);
curCSS = curCSS.replace(getterRegex, varList[theVar]);
// now check for any getters that are left that have fallbacks
let getterRegex2 = new RegExp('var\\(\\s*.+\\s*,\\s*(.+)\\)', 'g');
// console.log(getterRegex);
// console.log(curCSS);
let matches = curCSS.match(getterRegex2);
if (matches) {
// console.log("matches",matches);
matches.forEach(function(match) {
// console.log(match.match(/var\(.+,\s*(.+)\)/))
// find the fallback within the getter
curCSS = curCSS.replace(match, match.match(/var\(.+,\s*(.+)\)/)[1]);
});
}
// curCSS = curCSS.replace(getterRegex2,varList[theVar]);
};
// console.log(curCSS);
return curCSS;
},
// determine the css variable name value pair and track the latest
ratifySetters(varList) {
// console.log("varList:",varList);
// loop through each block in order, to maintain order specificity
for (let curBlock in varList) {
let curVars = varList[curBlock];
// console.log("curVars:",curVars);
// loop through each var in the block
curVars.forEach(function(theVar) {
// console.log(theVar);
// split on the name value pair separator
let matches = theVar.split(/:\s*/);
// console.log(matches);
// put it in an object based on the varName. Each time we do this it will override a previous use and so will always have the last set be the winner
// 0 = the name, 1 = the value, strip off the ; if it is there
cssVarPoly.ratifiedVars[matches[0]] = matches[1].replace(/;/, '');
});
};
// console.log(ratifiedVars);
},
// get the CSS file (same domain for now)
getLink(url, counter, success) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.overrideMimeType('text/css;');
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
// Success!
// console.log(request.responseText);
if (typeof success === 'function') {
success(counter, request);
}
} else {
// We reached our target server, but it returned an error
console.warn('an error was returned from:', url);
}
};
request.onerror = function() {
// There was a connection error of some sort
console.warn('we could not get anything from:', url);
};
request.send();
}
};
// hash = function(s){
// return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
// }
cssVarPoly.init();
// export default makeFit;
// stuff for hiding documentation for Opera Mini testing
document.querySelector('.hide-docs').addEventListener('click',function(event){
event.preventDefault();
document.querySelector('body').classList.toggle('hide-the-docs');
});
@stramel

This comment has been minimized.

Show comment
Hide comment
@loominade

This comment has been minimized.

Show comment
Hide comment
@loominade

loominade Nov 15, 2017

does not work for @import rules. Is there a reason why its not simply using document.styleSheets?

does not work for @import rules. Is there a reason why its not simply using document.styleSheets?

@gentle-media

This comment has been minimized.

Show comment
Hide comment
@gentle-media

gentle-media Jan 22, 2018

I had no joy with this in IE 11. The console said it had to do something with line 14, so I changed it to init: function () {and that fixed it for me.

gentle-media commented Jan 22, 2018

I had no joy with this in IE 11. The console said it had to do something with line 14, so I changed it to init: function () {and that fixed it for me.

@aaronbarker

This comment has been minimized.

Show comment
Hide comment
@aaronbarker

aaronbarker Mar 6, 2018

I'm the original author of the codepen. I have copied the polyfill into a repo so others can help with it. I have applied the fix from @gentle-media and logged the issue from @loominade as it is a bit bigger of an update.

https://github.com/aaronbarker/css-variables-polyfill

Thanks!

I'm the original author of the codepen. I have copied the polyfill into a repo so others can help with it. I have applied the fix from @gentle-media and logged the issue from @loominade as it is a bit bigger of an update.

https://github.com/aaronbarker/css-variables-polyfill

Thanks!

@jhildenbiddle

This comment has been minimized.

Show comment
Hide comment
@jhildenbiddle

jhildenbiddle Mar 9, 2018

I hope referencing another library here isn't considered poor form, but doing so may help others searching for more robust solution.

Long story short, I too stumbled on this gist after searching for a way to support CSS custom properties in legacy browsers. The limitations made this a no-go for me, so I created css-vars-ponyfill. I also needed to dynamically update property values on the client (not just do a one-time conversion to static values) which this library also handles.

Hope this helps someone, and hope you don't mind the link @aaronbarker!

jhildenbiddle commented Mar 9, 2018

I hope referencing another library here isn't considered poor form, but doing so may help others searching for more robust solution.

Long story short, I too stumbled on this gist after searching for a way to support CSS custom properties in legacy browsers. The limitations made this a no-go for me, so I created css-vars-ponyfill. I also needed to dynamically update property values on the client (not just do a one-time conversion to static values) which this library also handles.

Hope this helps someone, and hope you don't mind the link @aaronbarker!

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