Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get CSS value as written in stylesheet with jQuery or RegEx

Tags:

jquery

regex

css

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 {
  width: calc(100% - 50px);
}

I want to get the percentual value (100%) and the pixel value (50px) and I need to know what it is followed by (px, em, pt, %).

So basically:

  • get a value after calc( and before a seperator (+, -, *, /)
  • get a value after the seperator and before )
  • note that there could be more values than one!, e.g. calc(100% - 20px - 0.8em)

EDIT: Spudley talks of parsing the whole CSS stylesheet, but this might cause overhead. Because this a project for the heck of it, overhead is allowed, so you can go haywire and do whatever you want to accomplish this!

Thanks.

like image 555
Bram Vanroy Avatar asked Jan 08 '13 12:01

Bram Vanroy


1 Answers

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:

calc() function in Firefox

And this is how Google Chrome (v.24) treats it:

calc() function in Chrome

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.

like image 69
Onur Yıldırım Avatar answered Sep 30 '22 04:09

Onur Yıldırım