【JS】CSS変数の全ての値を取得しJSオブジェクトに変換-2
こんにちは!今日は前回に引き続き、
全てのCSS変数を取得する方法について書きます。
getPropertyAllのおさらい
:rootに記述された全てのCSS変数をJavaScriptの{key:value}方式で返す関数「getPropertyAll()」を見ていきます。
前回トチ狂ったコードを書いてしまいましたので、今回はコード程よく8分割し順番に解説したいと思います。
コード
8分割したコードはこちら。挙動は前回のコードと同じです。
:root {
--mount-fuji: 3776000mm;
--everest: 8848000mm;
}
const getPropertyAll = () => {
// 1
const CSSStyleSheets = [...document.styleSheets].map(sheet => [
...sheet.cssRules,
])
// 2
const CSSStyleSheetIncludingRoot = CSSStyleSheets.find(sheet =>
sheet.find(rule => rule.selectorText === ':root'),
)
// 3
const CSSStyleRuleIncludingRoot = CSSStyleSheetIncludingRoot.find(
sheet => sheet.selectorText === ':root',
)
// 4
const CSSText = CSSStyleRuleIncludingRoot.cssText
// 5
const splitedCSSText = CSSText.split(/:root\s*{|;|}/)
// 6
const trimmedCSSText = splitedCSSText.filter(text => text.trim())
// 7
const keyValueCSSArray = trimmedCSSText.map(text => [
text
.match(/.*?(?=:)/)[0]
.replace(/-/g, '_')
.trim(),
text.match(/(?<=:)(.*)/)[0].trim(),
])
// 8
const CSSObject = keyValueCSSArray.reduce(
(pre, cur) => ({
...pre,
[cur[0]]: cur[1],
}),
{},
)
return CSSObject
}
console.dir(getPropertyAll())
// {
// __mount_fuji: 3776000mm,
// __everest: 8848000mm,
// }
1.HTML Collectionを配列に変換
全てのCSSファイル(styleSheets)と全てのCSSルール(cssRules)は共にHTML Collectionです。
HTML Collectionはループ処理できないので、スプレッド演算子と配列(ブラケット)を用いて通常の配列に変換しています。
結果として、CSSStyleSheetsには全てのCSSファイルとそれらに含まれる全てのCSSルールが 2次元配列として代入されます。
// 1
const CSSStyleSheets = [...document.styleSheets].map(sheet => [
...sheet.cssRules,
])
2.3.4.セレクタ:rootのみを抽出
基本的にCSS変数は:rootで宣言するため、上記の2次元配列から:rootを含むもののみを抽出します。
抽出には様々な方法があるかと思いますが、今回は配列のうち最初に該当する要素をreturnする.findメソッドを使います。
これによって、2.には内包するCSSルールのうちいずれかに:rootセレクタを含むCSSファイルが、
3.にはそのCSSファイルの内包するCSSルールのうち:rootセレクタを含むCSSルールが代入されます。
4.では3.に代入したCSSルールのうち、セレクタと宣言部(:root{…} ←この部分全て)を抽出します。
この時点で全てのCSS変数が取得できましたが、まだ一つの長い文字列になっている状態です。
以後は正規表現を用いてこの巨大な文字列から必要分を切り出していきます。
// 2
const CSSStyleSheetIncludingRoot = CSSStyleSheets.find(sheet =>
sheet.find(rule => rule.selectorText === ':root'),
)
// 3
const CSSStyleRuleIncludingRoot = CSSStyleSheetIncludingRoot.find(
sheet => sheet.selectorText === ':root',
)
// 4
const CSSText = CSSStyleRuleIncludingRoot.cssText
5.6.7.正規表現による必要部分の抽出
5.では4.のうち宣言部(:root{この部分})のみを抽出し、1行ごとに配列化します。 また、6.では5.のうち半角スペースを除きます。
7.ではCSSのプロパティと値を長さ2の配列の各々に代入します。 また、この時CSS変数で用いる—(ハイフン)をJSオブジェクトで用いる__(アンダースコア)に変換しています。
// 5
const splitedCSSText = CSSText.split(/:root\s*{|;|}/)
// 6
const trimmedCSSText = splitedCSSText.filter(text => text.trim())
// 7
const keyValueCSSArray = trimmedCSSText.map(text => [
text
.match(/.*?(?=:)/)[0]
.replace(/-/g, '_')
.trim(),
text.match(/(?<=:)(.*)/)[0].trim(),
])
8.JSオブジェクトへの変換
7.のような[‘foo’, ‘bar’]となっている長さ2の配列を {‘foo’: ‘bar’}の形式でオブジェクトに代入していき、1つの巨大なオブジェクトにします。
ここも色々な書き方ができますね:)今回は関数型にしていますが、 forループで手続き型的に書けばもう少し分かりやすくなるかも…
// 8
const CSSObject = keyValueCSSArray.reduce(
(pre, cur) => ({
...pre,
[cur[0]]: cur[1],
}),
{},
)
結論
以上がCSS変数の全ての値を取得しJSオブジェクトに変換する方法についての解説になります!すごく長丁場。
これを応用すれば、プロキシ等を用いてJSオブジェクトの状態をCSS変数に完コピすることもできるかも…しれません。
さらにReduxやVuexのオブジェクトと組み合わせればCSS変数の頻繁な変更とかもできて夢が広がりそうですね♥
誰かやってみてくれ
以上よ。