CSSセレクタで属性だけじゃなく、スタイルもクエリーできちゃうとすごく便利(かも)

CSSセレクタって、CSSのルールで絞り込みはできても、CSS(スタイル)で絞り込みができないんだ…」って思ったことはありませんか?

実験的なテーマになりますが、styleプロパティの値を、CSSセレクタでクエリーできるように構文を拡張してみました。
# 構文や機能に対する改善案をお待ちしています。

スタイルで絞込みを行う場合の基本的な構文は、E { prop operator value }

以下のバリエーションを用意しました。
prop の値は、計算済みの値(currentStyle または getComputedStyle())の値と比較します。
IEで、currentStyle(element).width === "auto" なケースがありますが、そのような場合でも "auto" を px単位の値に自動的に変換したうえで比較します(つまり div{width="auto"} はヒットしません)

E { prop : value } プロパティの値がvalueと等しいE要素(数値または文字列として評価)
E { prop = value } プロパティの値がvalueと等しいE要素(数値または文字列として評価)
E { prop != value } プロパティの値がvalueと等しくないE要素(数値または文字列として評価)
E { prop *= value } プロパティの値がvalueを含むE要素(文字列として評価)
E { prop ^= value } プロパティの値がvalueで始まるE要素(文字列として評価)
E { prop $= value } プロパティの値がvalueで終わるE要素(文字列として評価)
E { prop /= value } プロパティの値が正規表現valueにマッチするE要素(文字列として評価)
E { prop /= "value"flag } プロパティの値が正規表現value(+flag)にマッチするE要素(文字列として評価)
E { prop >= value } プロパティの値がvalue以上の値を持つE要素(数値として評価)
E { prop <= value } プロパティの値がvalue以下の値を持つE要素(数値として評価)
E { prop &= value1-value2 } プロパティの値がvalue1からvalue2の範囲を持つE要素(数値として評価)

{ prop operator value } は、クォーテーション( " または ' )をつけて、{prop="value"} や {prop='value'} とすることもできます。

属性値でも正規表現が使える

class属性で正規表現が使えると便利なので、属性値の検索でも構文を拡張しています。
flag には i が使えます。

E [ attr /= value ] attr属性の値が正規表現valueにマッチするE要素(文字列として評価)
E [ attr /= "value"flag ] attr属性の値が正規表現value(+flag)にマッチするE要素(文字列として評価)
div[class/="Plugin[a-z]+_Config"i]
div[title/="hello"i]

色の扱いについて

色は数値化してから比較します。
以下は全て同じ式として評価します。

文字色が RED 以下のdiv要素を選択
div{color<=16711680}
div{color<=0xFF0000}
div{color<=#ff0000}
div{color<=red}
div{color<=rgb(255,0,0)}
  • transparent は 0x000000 と同じ値になります(alpha値を無視します)。
  • red は RED でも同じです。
  • #fff も使えます。
  • W3C Named Color ( "pink" とか) も使えます。

背景色を指定する場合は、backgroundColor を使用します。簡略化プロパティ(background)は使えません
div{ backgroundColor <= rgba(255,255,255,1) }

比較オペレータ( :  や  =  や  != )の場合も、数値として比較します。
div{ color :  red } これは div{color=0xFF0000}  と一緒
div{ color =  red } これは div{color=0xFF0000}  と一緒
div{ color != red } これは div{color!=0xFF0000} と一緒

不透明度の扱いについて

不透明度は0.0〜1.0の数値として扱います。

不透明度が0.5〜1.0のdiv要素を選択
div{opacity&=0.5-1}

幅と高さについて

幅(width)と高さ(height)はpx単位の数値として扱います。
スタイルシート上でptやem単位の値を設定されている場合でも、px に変換後の値で比較します。

positionがabsoluteまたはfixedで、leftが100px以下のdiv要素を選択

<style>
div { position: absolute; left: 10em; }
</style>
div{position/=absolute|fixed}{left<=100} // ヒットするかどうかはフォントサイズ(emの値)次第

正規表現オペレーター(/=)について

正規表現オペレータは、属性の検索とスタイルの検索の両方で使えます。
正規表現にはフラグ(i)が指定できます。
フラグや文字クラス([...])を指定する場合は 前後をクォーテーション( " または ' )で囲む必要があります。

class属性を正規表現で検索
div[class/=^(hoge|piyo)$]

class属性を正規表現で検索(文字クラス + フラグあり)
div[class/="[a-z][\d]{2}"i]

colorプロパティを正規表現で検索(文字として比較)
div{color/="^(red|blue|green)$"}

レンジオペレーター(&=)について

&= は、数値の範囲を指定するオペレータです、にょろ(~)で連結された値を2つ指定します(にょろが無い場合はエラーになります)

E{color&=red-blue}

比較は数値で行われます。
レンジオペレータは、比較演算子( <= や >= )を、2度使用するよりも低コストでより速い比較ができます。

こうではなく、
div { color <= red }{ color >= blue }

こうする
div { color &= red~blue }

これでもいい
div { color &= blue~red }

使用例

構文を拡張したCSSセレクタとタイマーを組み合わせることで、簡単なアニメーションを組むことも可能です。
# getComputedStyle()が重いのと、例題では forEach を使ってるのでちょっとモッサリしています

こちらで実際に試せます

<html><head><title>CSS Selector extend operator</title>
<style>
.abs {
  position: absolute; top: 20em; left: 10em;
  width: 100px; height: 100px;
  background-color: gray;
  opacity: 0.1;
}
.fxd {
  position: fixed; top: 30em; left: 10em;
}
</style>
<script type="text/javascript" src="uupaa.debug.js"></script>
</head>
<body>
<div>
 <ul>
  <li>list1</li>
  <li>list2</li>
  <li style="color: red">list3</li>
  <li style="opacity: 0.1">list4</li>
 </ul>
</div>
<div class="abs">absolute</div><div class="abs fxd">fixed</div>
<script>
function boot() {
  uu.css("div ul>li:nth-child(-n+3)").forEach(function(v) {
   v.style.backgroundColor = "silver";
  });
  uu.css("li{ color: #ff0000 }").forEach(function(v) {
   v.style.backgroundColor = "green";
  });
  uu.css("li{ width &= 200~600 }").forEach(function(v) {
   v.style.backgroundColor = "pink";
  });
  var end = 0;
  (function loop() {
    uu.css("{ opacity <= 0.99 }").forEach(function(v) {
     uu.style.setOpacity(v, +0.02, 1);
    });
    uu.css("div{ position /= absolute|fixed }{ left <= 300 }").forEach(function(v) {
      v.style.left = (parseFloat(uu.style(v).left) + 2) + "px";
    });
    ++end < 200 && setTimeout(loop, 10);
  })();
}
</script></body></html>

うーん。

ちょっと例が手抜きすぎて、便利さをうまくアピールできてないかも。

反省会

  • 機能は追加したけど、追加したことによる速度低下は発生しないようにがんばって実装した。
    • むしろ、CSSセレクタとしての精度/速度は前回のものよりも、さらにちょっとだけ向上した。
    • 特にノード数が多い(1000〜)複雑なページでの属性セレクタの速度を重点的に改善(書き直し)
  • class属性が正規表現でフィルタリングできると便利。
  • スタイルで要素が絞り込めるようになると、特定用途ではずいぶんとコードが短くなる。
    • 今はしがない支流(独自機能)だけど、使う人が増えればみんながハッピーになれるかもね。
  • ブラウザが返すバラバラな結果を合わせ込んだ上で比較する(やや強引な)形をとっているため、環境依存度が高い。
    • 今は良くても新ブラウザが登場するたびに作業が発生しそうな、ちょっとコスト高な機能。