7

我正在查看 CSS3 calc(),我想知道是否可以使用 jQuery(或 RegEx)从输入字符串中减去一个值。

例如:

div {
  width: calc(100% - 50px);
}

我想得到百分比值 ( 100%) 和像素值 ( 50px),我需要知道它后面是什么 ( px, em, pt, %)。

所以基本上:

  • calc(在分隔符 ( +, -, *, /)之后和之前获取一个值
  • 在分隔符之后和之前获取一个值)
  • 请注意,值可能不止一个!例如calc(100% - 20px - 0.8em)

编辑: Spudley 谈到解析整个 CSS 样式表,但这可能会导致开销。因为这是一个非常糟糕的项目所以开销是允许的,所以你可以乱七八糟地做任何你想做的事情!

谢谢。

4

3 回答 3

5

您不应该在现实生活中的应用程序中真正这样做,甚至不需要这样做,但既然您说这只是为了好玩;这是有趣的部分:

是的,你可以做到。通过 AJAX 调用加载 CSS 文件不是唯一的方法,但它可能是使其真正跨浏览器的唯一(但效率低下)的方法。即使下面的声明也不能使它真正跨浏览器,因为并非所有浏览器都支持calc()该功能,并且它是 CSS3 功能。

编辑:截至 2018 年,calc所有现代浏览器都支持。

div {
    width: 300px; /* a fallback value for old browsers */
    width: -webkit-calc(100% - 50px);
    width: -moz-calc(100% - 50px);
    width: calc(100% - 50px);
}

可以从中获取原始 CSS 代码document.styleSheets及其规则。该cssText规则的属性将为您提供完整的声明。但是不同的浏览器可能会以不同的方式解析具有calc()函数的值。

我将用一个更复杂的例子来看看浏览器是如何处理这个calc()函数的:

calc(100% - -50px*6 + 4em/2);

这就是Firefox (v.18) 处理它的方式:

Firefox 中的 calc() 函数

这就是Google Chrome (v.24) 的处理方式:

Chrome 中的 calc() 函数

如图所示; calcFF按原样获取非前缀值,Chrome 获取-webkit前缀值并使用嵌套括号重新解析它(如果需要)。如果你没有在 Chrome 中声明它 -webkit;它将完全忽略该值。因此,我们在操作 calc 语句时应该考虑到这些。

现在,使用复杂的示例;我们将首先获取 calc() 函数中的语句:

"100% - -50px*6 + 4em/2"

然后将语句的元素分解成一个数组:

["100%", "-", "-50px", "*", "6", "+", "4em", "/", "2"]

最后,处理数组项的值和单位,使它们以编程方式可用(如您所愿):

[{ value:100, unit:"%" }, "-", { value:-50, unit:"px" }, "*", { value:6, unit:undefined }, "+", { value:4, unit:"em" }, "/", { value:2, unit:undefined }]

上面的最终结果包括值对象和运算符(按顺序)。

在进一步阅读之前;请注意,下面的代码并未在所有浏览器和情况下完全测试。它不处理嵌套括号解析(像 Chrome 那样)或具有多个或组合 calc() 函数的值。如果你想测试这个;我推荐 Firefox,因为它不解析嵌套括号,或者您可以扩展代码以启用对它们的支持。

// 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; }

就是这样。正如我所提到的,我不会这样做,但知道是否有可能总是很好。

注意:既然你提到了制作一个 jQuery 插件(为了好玩);你真的不需要 jQuery。并且像这样调用函数$('div').css('width')只会给你计算的值,而不是原始的 calc 语句。

于 2013-01-26T16:25:29.763 回答
1

这里简短的回答是否定的,你不能那样做。浏览器 API 不会为您提供原始 CSS 代码,因此您无法按照您的要求进行操作。

实现此目的的唯一方法是将样式表文件手动加载到您的 javascript 中(即通过 Ajax 调用),然后对其进行解析。这对浏览器来说是很多开销。

这是一些 polyfill 脚本用来实现某些浏览器中不可用的 CSS 功能的一种技术(例如,这个脚本用于 IE 中的媒体查询支持,但是对于您正在做的检查值,我会说它比它更努力值得像这样的小问题。

如果您愿意冒险并在您的 JS 代码中手动解析整个 CSS 文件只是为了检索 calc 值,那么您最好的选择可能是从现有的 polyfill 脚本之一中复制解析代码。但是,如果我站在你的立场上,我会为潜在问题找到替代解决方案,而不是试图将这个想法强加于其合乎逻辑的结论。

我想根本的问题是这个问题确实需要转过头来:为什么你有一个calc具有固定值的CSS 100%50px如果你不知道代码中这些值是什么?的重点calc是允许您指定已知值的组合。这不是一些未知的用户输入值(或者至少不应该是),那么为什么您的 Javascript 不知道这些值呢?

如果您的 JS 和 CSS 代码之间存在脱节,您应该考虑如何在比浏览器更高的链上处理它,因为即使您确实设法从calc使用 Javascript 中获取值,这意味着浏览器每次页面加载时都必须一遍又一遍地进行相同的处理,以获取每次相同的值。这是很多浪费的努力。

于 2013-01-20T11:36:51.070 回答
0

假设其中没有使用嵌套括号calc(...),并假设对整个 css 进行一些正则表达式匹配的开销是可以接受的,下面的代码

  1. 使用jQuery.get()加载你的 CSS ,
  2. 搜索任何 : calc(...);(有点脏但可能就足够了),
  3. 提取大括号之间的任何内容,
  4. 删除任何运算符和周围的空格,
  5. 警告每个操作数,包括其单位。

您当然必须根据自己的需要进行一些调整,但我认为这基本上是您想要的:

$(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]);
            }
        }
    });
});
于 2013-01-21T19:37:14.253 回答