Skip to content

Commit bea9807

Browse files
author
Aaron Barker
committed
Initial checkin of files from https://codepen.io/aaronbarker/pen/MeaRmL
- pretty much a straight copy with minor tweaks to make work outside of codepen - scss to straight css
1 parent cfabd66 commit bea9807

File tree

4 files changed

+364
-1
lines changed

4 files changed

+364
-1
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
# css-variables-polyfill
1+
# CSS Variables Polyfill
22
A basic polyfill for CSS Variables/custom-properties
3+
4+
This is an attempt at a very basic [CSS variables (custom properties)](https://drafts.csswg.org/css-variables/) polyfil. In reality this is more of a partial polyfill as it will not cover variables inside of variables, DOM scoping or anything else "fancy". Just taking variables declared anywhere in the CSS and then re-parsing the CSS for var() statements and replacing them in browsers that don't natively support CSS variables.
5+
6+
According to [caniuse.com](https://caniuse.com/#feat=css-variables), of current browsers only IE, Edge and Opera Mini do not support CSS variables. This polyfil appears to work on all three really well. I don't see why this wouldn't work on older browsers as well, but I haven't been able to test it on them yet.

polyfill.js

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
TODO:
3+
X Maybe account for defaults: color: var(--header-color, blue);
4+
- Verify cross domain working or not (it is working from dropbox)
5+
- Option to wait to apply anything until all <link>s are parsed or inject what we have and update as each <link> returns
6+
- Need to test on a more complex CSS file
7+
- 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
8+
- Need to test more complex values like rgba(255,0,0,0.5); and something with !important
9+
- Try multiple links
10+
- Local links
11+
- Ajax driven site, or CSS added later the top of the stack
12+
*/
13+
let cssVarPoly = {
14+
init() {
15+
// first lets see if the browser supports CSS variables
16+
// 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
17+
// Edge supports supports, so check for actual variable support
18+
if (window.CSS && window.CSS.supports && window.CSS.supports('(--foo: red)')) {
19+
// this browser does support variables, abort
20+
console.log('your browser supports CSS variables, aborting and letting the native support handle things.');
21+
return;
22+
} else {
23+
// edge barfs on console statements if the console is not open... lame!
24+
console.log('no support for you! polyfill all (some of) the things!!');
25+
document.querySelector('body').classList.add('cssvars-polyfilled');
26+
}
27+
28+
cssVarPoly.ratifiedVars = {};
29+
cssVarPoly.varsByBlock = {};
30+
cssVarPoly.oldCSS = {};
31+
32+
// start things off
33+
cssVarPoly.findCSS();
34+
cssVarPoly.updateCSS();
35+
},
36+
37+
// find all the css blocks, save off the content, and look for variables
38+
findCSS() {
39+
let styleBlocks = document.querySelectorAll('style:not(.inserted),link[type="text/css"]');
40+
41+
// we need to track the order of the style/link elements when we save off the CSS, set a counter
42+
let counter = 1;
43+
44+
// loop through all CSS blocks looking for CSS variables being set
45+
[].forEach.call(styleBlocks, function(block) {
46+
// console.log(block.nodeName);
47+
let theCSS;
48+
if (block.nodeName === 'STYLE') {
49+
// console.log("style");
50+
theCSS = block.innerHTML;
51+
cssVarPoly.findSetters(theCSS, counter);
52+
} else if (block.nodeName === 'LINK') {
53+
// console.log("link");
54+
cssVarPoly.getLink(block.getAttribute('href'), counter, function(counter, request) {
55+
cssVarPoly.findSetters(request.responseText, counter);
56+
cssVarPoly.oldCSS[counter] = request.responseText;
57+
cssVarPoly.updateCSS();
58+
});
59+
theCSS = '';
60+
}
61+
// 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
62+
cssVarPoly.oldCSS[counter] = theCSS;
63+
counter++;
64+
});
65+
},
66+
67+
// find all the "--variable: value" matches in a provided block of CSS and add them to the master list
68+
findSetters(theCSS, counter) {
69+
// console.log(theCSS);
70+
cssVarPoly.varsByBlock[counter] = theCSS.match(/(--.+:.+;)/g) || [];
71+
},
72+
73+
// run through all the CSS blocks to update the variables and then inject on the page
74+
updateCSS() {
75+
// first lets loop through all the variables to make sure later vars trump earlier vars
76+
cssVarPoly.ratifySetters(cssVarPoly.varsByBlock);
77+
78+
// loop through the css blocks (styles and links)
79+
for (let curCSSID in cssVarPoly.oldCSS) {
80+
// console.log("curCSS:",oldCSS[curCSSID]);
81+
let newCSS = cssVarPoly.replaceGetters(cssVarPoly.oldCSS[curCSSID], cssVarPoly.ratifiedVars);
82+
// put it back into the page
83+
// first check to see if this block exists already
84+
if (document.querySelector('#inserted' + curCSSID)) {
85+
// console.log("updating")
86+
document.querySelector('#inserted' + curCSSID).innerHTML = newCSS;
87+
} else {
88+
// console.log("adding");
89+
var style = document.createElement('style');
90+
style.type = 'text/css';
91+
style.innerHTML = newCSS;
92+
style.classList.add('inserted');
93+
style.id = 'inserted' + curCSSID;
94+
document.getElementsByTagName('head')[0].appendChild(style);
95+
}
96+
};
97+
},
98+
99+
// parse a provided block of CSS looking for a provided list of variables and replace the --var-name with the correct value
100+
replaceGetters(curCSS, varList) {
101+
// console.log(varList);
102+
for (let theVar in varList) {
103+
// console.log(theVar);
104+
// match the variable with the actual variable name
105+
let getterRegex = new RegExp('var\\(\\s*' + theVar + '\\s*\\)', 'g');
106+
// console.log(getterRegex);
107+
// console.log(curCSS);
108+
curCSS = curCSS.replace(getterRegex, varList[theVar]);
109+
110+
// now check for any getters that are left that have fallbacks
111+
let getterRegex2 = new RegExp('var\\(\\s*.+\\s*,\\s*(.+)\\)', 'g');
112+
// console.log(getterRegex);
113+
// console.log(curCSS);
114+
let matches = curCSS.match(getterRegex2);
115+
if (matches) {
116+
// console.log("matches",matches);
117+
matches.forEach(function(match) {
118+
// console.log(match.match(/var\(.+,\s*(.+)\)/))
119+
// find the fallback within the getter
120+
curCSS = curCSS.replace(match, match.match(/var\(.+,\s*(.+)\)/)[1]);
121+
});
122+
123+
}
124+
125+
// curCSS = curCSS.replace(getterRegex2,varList[theVar]);
126+
};
127+
// console.log(curCSS);
128+
return curCSS;
129+
},
130+
131+
// determine the css variable name value pair and track the latest
132+
ratifySetters(varList) {
133+
// console.log("varList:",varList);
134+
// loop through each block in order, to maintain order specificity
135+
for (let curBlock in varList) {
136+
let curVars = varList[curBlock];
137+
// console.log("curVars:",curVars);
138+
// loop through each var in the block
139+
curVars.forEach(function(theVar) {
140+
// console.log(theVar);
141+
// split on the name value pair separator
142+
let matches = theVar.split(/:\s*/);
143+
// console.log(matches);
144+
// 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
145+
// 0 = the name, 1 = the value, strip off the ; if it is there
146+
cssVarPoly.ratifiedVars[matches[0]] = matches[1].replace(/;/, '');
147+
});
148+
};
149+
// console.log(ratifiedVars);
150+
},
151+
152+
// get the CSS file (same domain for now)
153+
getLink(url, counter, success) {
154+
var request = new XMLHttpRequest();
155+
request.open('GET', url, true);
156+
request.overrideMimeType('text/css;');
157+
request.onload = function() {
158+
if (request.status >= 200 && request.status < 400) {
159+
// Success!
160+
// console.log(request.responseText);
161+
if (typeof success === 'function') {
162+
success(counter, request);
163+
}
164+
} else {
165+
// We reached our target server, but it returned an error
166+
console.warn('an error was returned from:', url);
167+
}
168+
};
169+
170+
request.onerror = function() {
171+
// There was a connection error of some sort
172+
console.warn('we could not get anything from:', url);
173+
};
174+
175+
request.send();
176+
}
177+
178+
};
179+
180+
// hash = function(s){
181+
// return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
182+
// }
183+
cssVarPoly.init();

test.css

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
:root {
2+
--externalcolor: red;
3+
--samename: orange;
4+
--samename: #0f0;
5+
--foo: green;
6+
--FOO: #0f0;
7+
--halfsuccess: orange;
8+
--success: green;
9+
--success2: #0f0;
10+
}
11+
12+
html {
13+
font-family: var(--fontsans);
14+
}
15+
16+
.success {
17+
color: green;
18+
}
19+
20+
.fail {
21+
color: red;
22+
}
23+
24+
span {
25+
display: inline-block;
26+
margin: 5px;
27+
}
28+
29+
.samename {
30+
color: var(--samename);
31+
}
32+
33+
.demo1 {
34+
color: #f00;
35+
color: var(--success);
36+
}
37+
38+
.demo2 {
39+
color: #f00;
40+
color: var(--success2);
41+
}
42+
43+
.demo3 {
44+
color: #f00;
45+
color: var(--halfsuccess);
46+
color: var(--success);
47+
}
48+
49+
.demo4 {
50+
color: red;
51+
border-color: #f00;
52+
}
53+
54+
.inlineoverlink {
55+
color: #f00;
56+
}
57+
58+
p {
59+
padding: var(--spacing-l);
60+
}
61+
62+
.lower {
63+
color: var(--foo);
64+
}
65+
66+
.upper {
67+
color: var(--FOO);
68+
}
69+
70+
.externalcolor {
71+
color: var(--externalcolor);
72+
}
73+
74+
.fallback {
75+
color: #f00;
76+
color: var(--wrongname, green);
77+
}
78+
79+
.supports {
80+
color: green;
81+
}
82+
.supports .no {
83+
display: none;
84+
}
85+
86+
.showforpolyfill {
87+
display: none;
88+
}
89+
90+
.cssvars-polyfilled .supports {
91+
color: red;
92+
}
93+
.cssvars-polyfilled .supports .no {
94+
display: inline;
95+
}
96+
.cssvars-polyfilled .showforpolyfill {
97+
display: inline;
98+
}
99+
.cssvars-polyfilled .hideforpolyfill {
100+
display: none;
101+
}
102+
103+
.hide,
104+
.hide-the-docs .documentation {
105+
display: none;
106+
}
107+
108+
/* declare some font-family stuff at bottom of file to reflect on stuff above it*/
109+
:root {
110+
--fontsans: arial;
111+
}

test.html

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>Polyfill test</title>
8+
<link rel="stylesheet" type="text/css" media="all" href="test.css">
9+
<link rel="stylesheet" type="text/css" media="all" href="http://aaronbarker.net/cssvars/vars.css?a">
10+
</head>
11+
<body>
12+
<div class="documentation">
13+
<!-- Copy below for codepen update -->
14+
<h1>CSS Variables Polyfill</h1>
15+
<p>
16+
This is an attempt at a very basic <a href="https://drafts.csswg.org/css-variables/">CSS variables (custom properties)</a> polyfil. In reality this is more of a <em>partial</em> polyfill as it will not cover variables inside of variables, DOM scoping or anything else "fancy". Just taking variables declared anywhere in the CSS and
17+
then re-parsing the CSS for var() statements and replacing them in browsers that don't natively support CSS variables.
18+
</p>
19+
<p>According to <a href="http://caniuse.com/#feat=css-variables">caniuse.com</a>, of current browsers only IE, Edge and Opera Mini do not support CSS variables. This polyfil appears to work on all three really well. I don't see why this wouldn't work on older browsers as well, but I haven't been able to test it on them yet.</p>
20+
21+
<p>As far as we can tell your browser <span class="supports">does <span class="no">not</span> support</span> native CSS variables. <span class="showforpolyfill">That means if you see green tests results below, it is thanks to the polyfill :).</span> <span class="hideforpolyfill">All the green test results below are actually native CSS Variable support. Good job using a good browser :)</span></p>
22+
23+
<h3>Does this work on externally CSS files?</h3>
24+
<p>Yes!</p>
25+
<h3>Even ones loaded from another domain?</h3>
26+
<p>To go across domain, CSS needs to be served up with <code>Access-Control-Allow-Origin:*</code> headers.</p>
27+
28+
29+
</div>
30+
<a href="#d" class="hide-docs">Toggle documentation</a> (for Opera Mini vs Codepen issue)
31+
<style>
32+
:root {
33+
--newcolor: #0f0;
34+
}
35+
36+
.inlineoverlink {
37+
color: var(--success2);
38+
}
39+
</style>
40+
<h2>Tests</h2>
41+
<p>On mosts tests (unless otherwise noted) success will be green text. We start with a <code>color:red;</code> and then override it with a <code>color:var(--success);</code> (or similar) which is green.</p>
42+
<span class="samename">declare same variable over and over</span>
43+
<span class="demo1">no whitespace on var() calls</span>
44+
<span class="demo2">whitespace on var() calls</span>
45+
<span class="demo3">Multiple variables in same call. orange means first var worked, green var worked</span>
46+
<span class="inlineoverlink">orange if link won, green if style after link won</span>
47+
<span class="lower">--foo: lowercase foo</span>
48+
<span class="upper">--FOO: uppercase FOO</span>
49+
<span class="fallback">uses fallback <code>--var(--wrongname, green)</code></span>
50+
51+
<h2>Tests on external, cross-domain file</h2>
52+
<div class="documentation">
53+
<p><strong>Edge</strong> appears to be working well on Edge 13. Edge 12 was having some problems.</p>
54+
<p><strong>Opera mini</strong> seems to work well too. This demo fails because not all the page is displayed, but I think that is a codepen issue, not a polyfill issue. When the upper documentation is removed, all tests display well.</p>
55+
<p><strong>IE 11</strong> seems to do fine.</p>
56+
</div>
57+
58+
<span class="demo4">Gets stuff from external .css file. Should start red and change to green on LINK load. border proves the CSS loaded, missing colors means script didn't get parsed and reinserted</span>
59+
<span class="externalcolor">--externalcolor: should start red and change to green on LINK load</span>
60+
<span class="externalfallback">uses fallback. should be green</span>
61+
62+
<p>Another set of text under the test for Opera Mini testing.</p>
63+
<!-- Copy above for codepen update -->
64+
</body>
65+
</html>

0 commit comments

Comments
 (0)