【问题标题】:Add prefix to css rules with javascript使用 javascript 为 css 规则添加前缀
【发布时间】:2016-01-19 00:44:04
【问题描述】:

我有一个需要用 javascript 处理的带有以下 css 的字符串

h1
{
    color: red;
}

.info
{
    border: 1px dotted blue;
    padding: 10px;
}

#rect
{
    background: pink;
}

h2,
h3,
h4
{
    font-weight: bold;
}

table td:last td
{
    background: gainsboro;
}

如何在每个规则中添加前缀 .page 以使 css 不会中断?

我想要这个结果

.page h1
{
    color: red;
}

.page .info
{
    border: 1px dotted blue;
    padding: 10px;
}

...

现在,我通过寻找缩进来解决它,但是在这种情况下代码失败了

h1
{
color: red;
}

最后是

.page h1
{
.page color: red;
}

我也可以查找只有括号的行,但是这种情况会失败

h1 { color: red; }

我不想构建自己的 css 解析器,而我发现的那些主要处理应用于 DOM 中元素的 css 而不是带有 css 的字符串。是否有一个好的解析器或者可以通过其他方式实现?

【问题讨论】:

  • 你需要解析它!
  • 这个讨论在这种情况下可能会有所帮助:stackoverflow.com/questions/8302437/…
  • 您应该查看返工工具套件 (github.com/reworkcss/rework)。它解析和转换,并且可以处理字符串。众所周知,尝试使用正则表达式解析语言是一个糟糕的主意。正如你所发现的,你会想出一些你认为可行的解决方案,然后你会发现你的“解决方案”会失效。

标签: javascript css css-parsing


【解决方案1】:

这是一个为所有选择器添加类名前缀的函数。此函数正确处理 @ 规则:

var prefixCssSelectors = function(rules, className) {
  var classLen = className.length,
    char, nextChar, isAt, isIn;

  // makes sure the className will not concatenate the selector
  className += ' ';

  // removes comments
  rules = rules.replace( /\/\*(?:(?!\*\/)[\s\S])*\*\/|[\r\n\t]+/g, '' );

  // makes sure nextChar will not target a space
  rules = rules.replace( /}(\s*)@/g, '}@' );
  rules = rules.replace( /}(\s*)}/g, '}}' );

  for (var i = 0; i < rules.length-2; i++) {
    char = rules[i];
    nextChar = rules[i+1];

    if (char === '@' && nextChar !== 'f') isAt = true;
    if (!isAt && char === '{') isIn = true;
    if (isIn && char === '}') isIn = false;

    if (
      !isIn &&
      nextChar !== '@' &&
      nextChar !== '}' &&
      (
        char === '}' ||
        char === ',' ||
        ((char === '{' || char === ';') && isAt)
      )
    ) {
      rules = rules.slice(0, i+1) + className + rules.slice(i+1);
      i += classLen;
      isAt = false;
    }
  };

  // prefix the first select if it is not `@media` and if it is not yet prefixed
  if (rules.indexOf(className) !== 0 && rules.indexOf('@') !== 0) rules = className+rules;

  return rules;
}


// Some examples:
console.log(prefixCssSelectors('div { width: 100%; }', '.page'));

console.log(prefixCssSelectors('@charset "utf-8"; div { width: 100%; }', '.page'));

console.log(prefixCssSelectors('@media only screen { div { width: 100%; } p { size: 1.2rem; } } @media only print { p { size: 1.2rem; } } div { height: 100%; font-family: "Arial", Times; }', '.page'));

console.log(prefixCssSelectors('@font-face { font-family: "Open Sans"; src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"); } div { width: 100%; }', '.page'));

如果您只想拥有 htmlbody 选择器的类名,请添加:

rules = rules.replace(/( html| body)/g, '');

【讨论】:

  • 非常优雅的解决方案,在我的两个项目中使用过,但要小心@font-face。
  • 我在设置 isAt = true 的行中做了一个简单的解决方法,方法是检查 nextChar 不是 'f',就像 if (char === '@' &amp;&amp; nextChar !== 'f') isAt = true;
【解决方案2】:

您提出了一个问题,您认为如何解决您的实际问题,这似乎是,我如何将一些 CSS 仅应用于文档的一个部分?

最终,解决方案将是&lt;style scoped&gt;。在我们等待的同时,您可以尝试使用诸如 https://github.com/PM5544/scoped-polyfillhttp://thomaspark.co/2015/07/polyfill-for-scoped-css/https://github.com/thingsinjars/jQuery-Scoped-CSS-pluginhttps://github.com/thomaspark/scoper 之类的 polyfill。

如果你有一个 CSS 字符串(顺便说一句,你从哪里得到的?),那么要使用这些 polyfill,你可以简单地将一个 &lt;style scoped&gt; 元素插入到你的 DOM 中的 .page 元素下,包含 CSS你已经在字符串中了。

所有这些 polyfill 的主要优点是它们基于浏览器正确解析的 CSS,并且在出现问题的第一个迹象时不会中断,因为您不建议接受的正则表达式解决方案以及所有其他正则表达式解决方案,不可避免地会的。

【讨论】:

【解决方案3】:

如果你想使用sass,你可以简单地写类似

.page {
  h1 {
    color: red;
  }

  h2 {
    color: green;
  }
}

这将被编译成你想要的。但如果这不是一个选项,您必须编写自己的解析器。

【讨论】:

  • 我意识到这一点,但我做不到。我需要用 javascript 处理一个 css 字符串。
【解决方案4】:

一种解决方案,它循环遍历 CSS 规则并即时更新它们。

/* debug purpose */
var j = JSON.stringify;
var el = document.getElementById('el');
var log = function( val ){el.innerHTML+='<div>'+val+'</div>'}

var doIt = function(){

// you have to identify your stylesheet to get it
var ss = document.styleSheets[0];
// we get all the rules from this stylesheets
var rules = ss.cssRules;

// we loop over all rules
for( var i = 0 ; i < rules.length; i++){
  var rule = rules[i];
  
  var selector = rule.selectorText;
  var def = rule.cssText.replace(selector , '');
  
  // we update the selector
  var selector2 = selector.replace(/([^,]+,?)/g , '.page $1 ' ); 

  // we modify the rules on the fly !
  var def2 = def.replace('red' , 'green');
  def2 = def2.replace('blue' , 'red');
  def2 = def2.replace('pink' , 'lime');
  
  log(i);
  log( 'def : ' + def);
  log( 'modified : ' + def2);
  log( 'selector : ' + selector);
  log( 'updated : ' + selector2);
  log('----------');
  ss.deleteRule(i);// we remove the old 
  ss.insertRule(selector2 + def2 , i);// we add the new 
  
};

  
  // we check the stylecheet !
  log( ' checking the results');
  for( var i = 0 ; i < rules.length; i++){
  var rule = rules[i];
  
  var curRule = rule.cssText;
  log( i + ':  curRule => ' + curRule);
 
};
  
};
h1
{
    color: red;
}

.info
{
    border: 1px dotted blue;
    padding: 10px;
}

#rect
{
    background: pink;
}

h2,
h3,
h4
{
    font-weight: bold;
}

table td:last td
{
    background: gainsboro;
}
<div class='page'>
  <div id='rect'> RECT </div>
  <div class='info'> INFO </div>
<div>
<button onclick='doIt()'>update it</button>
<div id='el'><div>

【讨论】:

    【解决方案5】:

    看看正则表达式

    例如这样的:

    /([^]*?)({[^]*?}|,)/g
    

    应该在第一组中捕获您的 css 字符串中的所有选择器,并在第二组中捕获规则。然后你做这样的事情来替换匹配项

    var str = "your css text here";
    re = /([^]*?)({[^]*?}|,)/g;
    var new_str = str.replace(re, ".prefix $1 $2"); //prefixed css
    console.log(new_str); 
    

    如果你不熟悉正则表达式我建议阅读Eloquent Javascript的相关章节

    【讨论】:

    • 这将在 CSS 上失败,例如 "html, body { foo: bar; content: '}'; } div { color: red }"。它也会在.\{boo\} { color: red; } 等情况下失败。当然,这些都是人为的例子,但它们仍然表明你不能用正则表达式可靠地解析 CSS。
    • 对于这些情况,我想你可以修改正则表达式来解释转义/引用的大括号
    猜你喜欢
    • 2020-05-06
    • 2015-02-05
    • 1970-01-01
    • 2022-06-22
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多