I was looking at CSS3 calc() and I wondered whether it is possible to subtract a value from the input string with jQuery (or RegEx).
For example:
div
You shouldn't really do or even need this in a real-life application but since you say this is just for fun; here is the fun part:
Yes you can do it. Loading the CSS file via AJAX calls is not the only way but it may be the only (but inefficient) way to make it really cross-browser. Even the declaration below won't make it truly cross-browser since calc()
function is not supported by all browsers and it's a CSS3 feature.
EDIT: As of 2018, calc
is supported by all modern browsers.
div {
width: 300px; /* a fallback value for old browsers */
width: -webkit-calc(100% - 50px);
width: -moz-calc(100% - 50px);
width: calc(100% - 50px);
}
You can get the raw CSS code from document.styleSheets
and their rules. The cssText
property of the rule will give you the full statement. But different browsers may parse the values with a calc()
function differently.
I'll go with a more complex example to see how browsers treat the calc()
function:
calc(100% - -50px*6 + 4em/2);
This is how Firefox (v.18) treats it:
And this is how Google Chrome (v.24) treats it:
As shown; FF gets the non-prefixed calc
value as it is and Chrome gets the -webkit
prefixed value and re-parses it with nested parenthesis (if needed). If you don't declare it -webkit in Chrome; it will completely ignore the value. So, we should take these into account when we manipulate the calc statement.
Now, using the complex example; we will first get the statement inside the calc() function:
"100% - -50px*6 + 4em/2"
Then dissolve the elements of statement into an array:
["100%", "-", "-50px", "*", "6", "+", "4em", "/", "2"]
Finally, process the values and units of the array items to make them programmatically usable (as you wanted):
[{ value:100, unit:"%" }, "-", { value:-50, unit:"px" }, "*", { value:6, unit:undefined }, "+", { value:4, unit:"em" }, "/", { value:2, unit:undefined }]
The final result above includes the value-objects and operators (in order).
Before reading further; note that the code below is not completely tested on all browsers and situations. It does not handle nested parenthesis parsing (like Chrome does) or, values with multiple or combined calc() functions. If you want to test this; I recommend Firefox since it does not parse nested parenthesis OR you can extend the code to enable support for them.
// Get the sheet you want to process. (assume we want to process the third sheet):
var sheet = document.styleSheets[2]; //you could also iterate all the sheets in a for loop
processRules(sheet);
/** Iterates through the rules of the specified style sheet;
* then dissolves and logs values including a calc() function.
*/
function processRules(sheet) {
var rules = sheet.cssRules // Mozilla, Safari, Chrome, Opera
|| sheet.rules; // IE, Safari
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
// Check if we have a calc() function in this rule
if (hasCalc(rule.cssText)) {
// Get the calculation statement inside the calc() function.
var statement = getCalcStatement(rule.cssText);
// Dissolve the statement into its elements and log.
console.log(dissolveCalcElements(statement));
}
}
}
/** Checks whether the CSS value includes a calc() function,
* (This is also for avoiding unnecessary regex.)
*/
function hasCalc(value) {
return value.toLowerCase().indexOf('calc(') >= 0;
}
/** Gets the full statement (string) inside a calc() function.
*/
function getCalcStatement(rule) {
if (!rule) return '';
var pattern = /calc\(([^\)]+)\).*/;
var match = pattern.exec(rule);
return match && match.length > 1 ? match[1] : '';
}
/** Splits the calc operation's elements (values and operators) and
* dissolves the values into objects with value and unit properties.
*/
function dissolveCalcElements(statement) {
// The CSS calc() function supports 4 basic math operations only:
// Addition (+), Subtraction (-), Multiplication (*), Division (/)
// White-spaces are very important in a calc statement.
// From Mozilla: "The + and - operators must always be surrounded by whitespace.
// The * and / operators do not require whitespace, but adding it for consistency is allowed, and recommended."
// We could use: statement.split(/(\s+[\+\-]\s+|\s*[\*\/]\s*)/);
// to include the operators inside the output array, but not all browsers
// support splicing the capturing parentheses into the array like that. So:
statement = statement.replace('*', ' * ').replace('/', ' / ');
var arr = statement.split(/\s+/);
console.log("arr", arr);
var calcElems = [];
for (var i = 0; i < arr.length; i++) {
var d = dissolveElement(arr[i]);
calcElems.push(d);
}
return calcElems;
}
/** Dissolves the value and unit of the element and
* returns either the operator or an object with "value" and "unit" properties.
*/
function dissolveElement(val) {
// Check if the value is an operator.
var ops = '+-*/';
if (ops.indexOf(val) >= 0) return val;
var o = {};
// CSS units in a calc statement can have all the regular units.
// According to W3C; they can also, can include a "vw" unit (stands for viewport).
var pattern = /([\+\-]?[0-9\.]+)(%|px|pt|em|in|cm|mm|ex|pc|vw)?/;
// Exec the value/unit pattern on the property value.
var match = pattern.exec(val);
// So we reset to the original value if there is no unit.
if (match) {
var v = match.length >= 2 ? match[1] : match[0];
o.value = toFloat(v); //parse value as float
o.unit = match.length >= 3 ? match[2] : '';
}
else {
o = { value:val, unit:''};
}
console.log("dissolve", match, val, o);
return o;
}
// Helper Functions
function toFloat(value) { return parseFloat(value) || 0.0; }
That's it. As I mentioned, I wouldn't do this but it's always good to know if anything is possible.
Note: Since you mentioned making a jQuery plugin (for fun); you don't really need jQuery for this. And calling functions like $('div').css('width')
will only give you the computed value, not the raw calc statement.
Assuming that no nested parentheses are used within calc(...)
and assuming that the overhead of doing some regex matching against the whole css is acceptable, the following code
: calc(...);
(a little dirty but probably sufficient),You surely have to do a few adjustments according to your needs but I think this is basically what you wanted:
$(document).ready(function() {
$.get("your.css", function(data) {
var re1 = /: calc\((.*)\);/g;
var re2 = /\s*[\-\+\/\*]\s*/;
while (match = re1.exec(data)) {
var arr = match[1].split(re2);
for (i in arr) {
alert(arr[i]);
}
}
});
});
The short simple answer here is no, you can't do that. The browser APIs won't give you the raw CSS code, so you can't do what you're asking.
About the only way you're going to achieve this is to load the stylesheet file into your javascript manually (ie via an Ajax call), and parse it. This is a lot of overhead for the browser.
It is a technique used by some polyfill scripts to implement CSS features that aren't available in certain browsers (eg this script for Media Queries support in IE, but for just checking values as you're doing I would say it's more effort than it's worth for a smaller question like this.
If you're willing to take the plunge and parse the whole CSS file manually in your JS code just for the sake of retrieving the calc values, then your best bet may be to crib the parsing code from one of the existing polyfill scripts. But if I were in your shoes I'd find an alternative solution to the underlying problem rather than trying to force this idea to its logical conclusion.
I guess the underlying problem is that question really needs to be turned on its head: Why do you have a CSS calc
with fixed values of 100%
and 50px
if you don't know in the code what those values will be? The whole point of calc
is to allow you to specify a combination of known values. This isn't some unknown user-entered value (or at least, it shouldn't be), so how come your Javascript doesn't know the values already?
If there is a disconnect between your JS and your CSS code, you should consider how to deal with it higher up the chain than the browser, because even if you do manage to get the values out of the calc
using Javascript, it means that the browser will have to be doing that same set of processing over and over again on every page load, for values that are the same every time. That's a lot of wasted effort.