CSS を JavaScript で拡張できないかと思っていた.デザイナーは簡単に表現できることが増えるし,エンジニアは保守性が上がってよさそうだな,ということを考えていた.
ところで
こういう提案はすでにあって,ブラウザの API 経由で CSS を拡張できる experimental な実装があるようだ.CSS Houdini
と呼ばれている.各ブラウザの実装状況は Is Houdini Ready Yet? というサイトで確認できる:
ちなみに Houdini
は フーディニ と発音するようで,どういう意味か調べたら An escape artist
という意味らしい.引田天功みたいな人ってことかな.信憑性が疑わしい.
CSS Houdini
CSS Houdini がどういう使い方ができるか調べてみたら,Chrome が先行していろいろ実装していてデモが確認できる https://googlechromelabs.github.io/houdini-samples/
Layout API
は JS 側で要素のレイアウトを構成したり,Paint API
は Canvas で要素を自由にレンダーできるようになっている.特に Paint API の例にある QR コードの例はおもしろい:
https://googlechromelabs.github.io/houdini-samples/paint-worklet/qr-code/
<textarea>
だけど QR コードがレンダーされている謎な状況になっている.エンコードするデータは --text
カスタムプロパティによって CSS に渡されている:
地味に重要な機能として Properties & Values API
というのがある.CSS のカスタムプロパティ (--prop
) の型をJS から定義できる.JS から CSS にデータを渡すのに使えるし,CSS アニメーションでは補完が効くようになったりするのが嬉しいポイントになっている.後に出てくる試みでもふんだんに使っている.
CSS Houdini+
ところで,CSS Houdini では関数やセレクタの拡張は現時点では特に見込みがなさそうだった.Parser API
はそれを試みようとしていそうだが,ステータスを見てもわかるようにまだまだ構想段階という感じがしている.なので,CSS メタプログラミングに傾倒してめちゃくちゃカスタマイズしたいという野望は当面実現しがたい.
これを現時点でがんばるなら,CSS をプリプロセスして変更した CSS + それのヘルパー JS を emit する仕組みが必要と思った.webpack とか parcel でひとまとめにできればそこまで煩わしくもない気もする.polyfill みたいにブラウザネイティブな API で同じことができるようになったとき,ネイティブ実装に移譲できるようになっているとか.と考えたところで babel を思い出して考えるのをやめた.
CSS を拡張したいという試み
さて,もし CSS Houdini のようなかたちで CSS のプロパティや関数,セレクタが拡張できるとしたらどうだろう.自分が思いついた「僕の考えたクールな CSS の拡張の提案」と現時点での実装をした.多くは CSS Houdini の機能を使っていて Chrome でないと完全な動作はしない代物になっている.
(※ 以下はすべてあったらいいなという妄想の構文なので,実際には使えないということを注意されたい)
::nth-letter(i)
疑似要素
指定した要素のある位置の文字を修飾するための疑似要素の提案:
<p id="my-paragraph">This is a paragraph.</p>
p#my-paragraph::nth-letter(2n + 1) { color: red; text-transform: uppercase; }
たとえば この例はこういう結果を得る:
なんの役に立つのか,というとクラシックな Web サイトでよく使われていた意外とマークアップがめんどうくさい
虹グラデーション文字を表現したい,という需要にも応えることができる.
今できる範囲で実現するとこうなる:
See the Pen :nth-letter(expression) by マンガーノ・伊藤 (@mangano_ito) on CodePen.
https://codepen.io/mangano_ito/pen/NWGMvBq
この例は適当だけど,実際は他の兄弟要素があっても影響が出ないように,Shadow DOM にしたり,innerHTML
で雑に置き換えたりしないように考慮しないといけないとか,そういう課題が山積みになっている.
ちなみに Chrome だと CSS Houdini の成果によりレインボー文字がアニメーションするようになっている. Firefox では動かないのでプログレッシブ・エンハンスメントという名の未対応になっている:
このアニメーションは先述した Properties & Values API でカスタムプロパティに型を定義することで実現している.そうすると値を @keyframes
で 0
〜 360
に無限ループで連続的に変化させることができるので,hsl
の色相が 0 度 〜 360 度でトランジションするようになっている:
:root { --angle: 0; animation: angle_around 1s linear 0s infinite; } @keyframes angle_around { 0% { --angle: 0; } 100% { --angle: 360; } } #letter { $h: calc(var(--angle) * 1deg); color: hsl($h, 75%, 50%); }
dataset(selector)
関数
<element data-prop="1234" />
において rule: dataset(prop);
で対応する data-prop
属性の値を使う関数.
attr(data-*)
すればいいのでは,という話題があるけど,attr
は content
以外の多くのプロパティで使えない感じがしている.あまねくプロパティで属性値の変化に応じてリアクティブにスタイルが変わってほしい.型は文脈によって推論されたい.
そうすれば,たとえばプログレスバー的なコンポーネントに対して data-progress
に進捗度合いを設定することによって,CSS だけでプログレスバーのアニメーションを実現できたりするだろう:
<p id="progress" data-progress="0.2">In Progress...</p>
#progress { $color: hsl(100, 50%, 50%); $progress: calc(dataset(progress) * 100%); background-image: linear-gradient( to right, $color 0%, $color $progress, transparent $progress, transparent 100% ); transition: background-image 0.25s ease-in-out 0.1s; }
現時点でこれを実現するためには JS を経由してカスタムプロパティを設定する.例によって Properties & Values API が使えない Firefox では動かない:
See the Pen dataset(selector) by マンガーノ・伊藤 (@mangano_ito) on CodePen.
https://codepen.io/mangano_ito/pen/gOazXov
ここでは MutationObserver
で data-progress
の変化をウォッチして,変化があったら --prop--progress
カスタムプロパティに設定するようになっている.JS → HTML → JS → CSS という遠回りな値のバケツリレーになっている.
scroll-y
プロパティ
これは単にビューポートのスクロールした値が渡ってくるプロパティ.Android の <CoordinatorLayout>
みたいなネイティブアプリのプロフィール画面でよくあるパララックスっぽい効果とかに活用できると思った:
これは現時点でも単に --scroll-y
に window.scrollY
を渡せばできる.
See the Pen CoordinatorLayout by マンガーノ・伊藤 (@mangano_ito) on CodePen.
https://codepen.io/mangano_ito/pen/PoPeear
例の理由で Firefox ではパララックスにはならないけど読める.requestAnimationFrame
でガチャガチャやっているので重い.ちなみに画像は CodePen で用意されている Assets 使っている.
しかしながら HTML と TypeScript はシンプルだが,まあ SCSS はとても汚い書き方になっている.Dev Tools のアニメーションのタイムラインを見るといかにも重そうな感じになっている:
CSS 側でいろいろ計算をやろうとするとカオスになってくる.スクロールの振る舞いはやっぱり JS で計算したほうがいいのでは,となって議論が一周してきたりした.
randomOf(values...)
関数
これは与えられた values...
からランダムに値を選択するための関数:
p { color: randomOf(red, blue, yellow); }
こうすると #my-class
はページをリロードするたびに red
, blue
, yellow
のどれかが毎回ランダムに選択されるようになる.こういう感じ:
See the Pen random_of(...values) by マンガーノ・伊藤 (@mangano_ito) on CodePen.
めちゃくちゃ地味だけど,これは業務で実際に実装する必要があったから欲しいと思っている人はいるはず.JS 側で動的に style
を変更したり,パターンごとに複数のクラスを生やして JS 側でクラスを与える必要がなくなる.それだけといえばそれだけ.
:within-viewport
疑似セレクタ
この疑似セレクタは 要素がビューポートに入っていれば有効になるセレクタだ.現時点では InsersectionObserver
を使ってクラスを当てることで実現している:
See the Pen :within-viewport by マンガーノ・伊藤 (@mangano_ito) on CodePen.
https://codepen.io/mangano_ito/pen/oNjdore
またもや地味.これくらいはわざわざ専用のセレクタにしなくても,普通に JS で class
を設定されるほうが素直な気がしている.
感想
意見がまとまらなかったので箇条書きにした: