CSS を JavaScript で操作する
- カテゴリ:
- JavaScript
- CSS
- コメント数:
- Comments: 0
◆ CSS 関係のオブジェクトいろいろ
これまで CSS を JavaScript で操作するときは element の style プロパティを直接書き換えるくらいでした
あとは直接 CSS を触らず 最初から CSS にクラスいろいろ用意しておいて JavaScript ではクラスを add/remove するだけ
また拡張機能から埋め込むときは
こういう風に最初に追加して ボタン押した時などに
みたいに innerHTML を変えて追加した CSS 全体を置き換えてました
DOM みたいな感じで CSS も JavaScript で扱えるように細かく管理されていて ひとつひとつのルール単位で変更できそうなので調べてみました
大きくは CSS と CSSRule と StyleSheet と CSSStyleDeclaration です
CSSRule と StyleSheet にはそれらのリスト版もあります
document.styleSheets にその document 内でロードされている stylesheet のリストが格納されています
これが StyleSheetList で配列みたいなものです
中には StyleSheet を継承した CSSStyleSheet があります
CSSStyleSheet には cssRules プロパティがあり これが CSSRuleList です
これも配列みたいなものです
CSSRuleList のそれぞれは CSSRule を継承したもので CSSStyleRule や CSSKeyframesRule などがあります
CSSStyleRule の style プロパティが CSSStyleDeclaration です
element の style プロパティも同じく CSSStyleDeclaration です
よくある JavaScript でスタイルを書き換える処理
は CSSStyleDeclaration のプロパティ color を書き換えています
CSSStyleDeclaration には全部の CSS のプロパティが存在します
未指定のものは空文字で指定したものにはその値が入っています
また cssText には {} の内側に書くルール全体のテキストが入っています
値が入ってるものをわかるようにするためか配列のような機能もあり
length プロパティに設定済みの数
0 から始まる数字のキーには設定済みの key 名が入っています
ここは入れた順(ルール順)になってるようです
style 自体にルールの文字列を代入することもできます
そうすると追加ではなく完全にリセットされて指定したプロパティだけの設定になります
配列要素は今回の max-width だけになっていて margin などは空文字にもどっています
important を確認するには getPropertyPriority を使います
important なら important の文字がでます
important でないなら空文字です
他には getPropertyValue, removeProperty, setProperty メソッドがあります
動きは名前通りです
setProerty は第三引数に "important" をつけると important にできます
removeProperty は一応返り値で削除したときの値が受け取れます
CSSStyleDeclaration は stylesheet のひとつひとつのルールに存在します
などのひとつひとつのルールが CSSRule になります
↑みたいな基本的なもの以外にも
こういう @ から始まる特殊なものがあります
@page は CSSPageRule で @media は CSSMediaRule のように対応しています
これは
のような普通のひとつのルールのことです
ひとつの {} が一つのルールなので
こんなにセレクタが複数になっても中が font-family と margin の 2 つになってもひとつの CSSStyleRule です
CSSStyleRule の style プロパティが上で書いた CSSStyleDeclaration です
cssText プロパティにはセレクタも入ったルールのテキストが入っています
selectorText にはセレクタ部分だけが入っています
CSSRule の中のどのタイプのルールなのかは type プロパティに数字で入っています
JavaScript では珍しめな enum 形式です
media や supports は条件に合っていた時に処理される CSSRuleList を cssRules プロパティとしてもっています
import だと stylesheet 自体をインポートなので styleSheet プロパティに CSSStyleSheet をもっています
keyframes だと内側には 0% のような特殊なセレクタをもつ CSSStyleRule なので CSSKeyframesRule のプロパティ cssRules には CSSStyleRule の代わりに CSSKeyframeRule (s がない単体版)をもっています
CSSKeyframeRule は CSSStyleRule と似たもので同じように style プロパティに CSSStyleDelaration があります
font-face では src などは CSSStyleDeclaration として表現されています
構造のサンプル
document.styleSheets に document がロードした CSSStyleSheet がリストになった StyleSheetList があります
href プロパティを見ることで style タグを使ってインラインで書いたか URL からロードしたかわかります
style タグなら href が null になっています
ownerNode プロパティが link または style タグの要素への参照になっています
反対に 要素から CSSStyleSheet を取得したいときは sheet プロパティを使います
ただし クロスオリジン制約を受けるので 別オリジンからロードしたものの cssRules は null になっていて参照できません
追加は insertRule 削除は deleteRule です
追加する場所やどのルールを消すかの指定には index を使います
互換性のためか古いプロパティも残ってるみたいです
追加: addRule ▶ insertRule
削除: removeRule ▶ deleteRule
ルールリスト: rules ▶ cssRules
個人的には古いの名前のほうが好きです
disabled プロパティがあるのでそれを true にすれば無効になります
link タグでロードしてる場合は link の属性に disabled をつけることもできます
style タグの場合は disabled 属性は効果なかったです
また ロード中に disabled の切り替えをすると変に混ざったような状態になるので onload 後に操作するのが無難です
disabled の付け替えを使えば テーマをつくるときなどに楽になります
body にテーマのクラスをつけることで切り替えて CSS 側は
のようにすべてのセレクタに テーマの名前のプレフィックスをつけるものは多い気がしますが disabled を JavaScript で切り替えればプレフィックスは無くても大丈夫です
例えばこんな感じで複数のテーマの CSS をロードしていたときに
こうやって指定のテーマ以外を disabled にすればいいです
new してみても Illegal constructor と言われますし インスタンスを作れないものなのかもしれません
使いみちですが CSS には escape と supports 関数がプロパティとして用意されています
記号をつけたクラスをつけます
エスケープしないと invalid なセレクタですと怒られてしまいます
Math みたいなプロパティしか使わないなら CSS 自体がオブジェクトでいいのに関数ということはどこかにインスタンスありそうな気もしますけど見つからないんですよね
要素の style プロパティに書くのは 避けたい(jQuery が DOM を荒らしていくような感じがする)ので出来る限り stylesheet の方でどうにかしたいです
でも普段からここまで細かくルール単位で制御するのは大変です
やっぱり 基本は class の書き換えだけで対処できるようにして 大量の要素のクラスを書き換えるような スタイルの方を変えたほうが楽な場合だけルールを書き換えるくらいがよさそうです
スタイル書き換えにしても書き換えそうなところだけ別のタグにしておいて innerHTML を直接書き換えた方が簡単でいいのかも
なので知ってて役立つ可能性が大きいのは disabled プロパティかと思います
あとは直接 CSS を触らず 最初から CSS にクラスいろいろ用意しておいて JavaScript ではクラスを add/remove するだけ
また拡張機能から埋め込むときは
var style = document.createElement("style")
style.innerHTML = `/* CSS */`
document.head.append(style)
style.innerHTML = `/* CSS */`
document.head.append(style)
こういう風に最初に追加して ボタン押した時などに
style.innerHTML = `.xxx{display: none;}`
みたいに innerHTML を変えて追加した CSS 全体を置き換えてました
DOM みたいな感じで CSS も JavaScript で扱えるように細かく管理されていて ひとつひとつのルール単位で変更できそうなので調べてみました
CSS 系オブジェクト
Chrome でみたところ CSS に関係しそうなオブジェクトはこんなのがありましたObject
CSS
CSSRule
CSSFontFaceRule
CSSGroupingRule
CSSImportRule
CSSKeyframeRule
CSSKeyframesRule
CSSMediaRule
CSSNamespaceRule
CSSPageRule
CSSStyleRule
CSSSupportsRule
CSSViewportRule
CSSRuleList
CSSStyleDeclaration
StyleSheet
CSSStyleSheet
StyleSheetList
CSS
CSSRule
CSSFontFaceRule
CSSGroupingRule
CSSImportRule
CSSKeyframeRule
CSSKeyframesRule
CSSMediaRule
CSSNamespaceRule
CSSPageRule
CSSStyleRule
CSSSupportsRule
CSSViewportRule
CSSRuleList
CSSStyleDeclaration
StyleSheet
CSSStyleSheet
StyleSheetList
大きくは CSS と CSSRule と StyleSheet と CSSStyleDeclaration です
CSSRule と StyleSheet にはそれらのリスト版もあります
使われてるところ
それぞれがどこにつかわれているのかはこんな感じですdocument.styleSheets
// StyleSheetList
document.styleSheets[0]
// CSSStyleSheet
document.styleSheets[0].cssRules
// CSSRuleList
document.styleSheets[0].cssRules[0]
// CSSStyleRule
// CSSFontFaceRule
// CSSKeyframesRule
// など
document.styleSheets[0].cssRules[0].style
// CSSStyleDeclaration
document.body.style
// CSSStyleDeclaration
// StyleSheetList
document.styleSheets[0]
// CSSStyleSheet
document.styleSheets[0].cssRules
// CSSRuleList
document.styleSheets[0].cssRules[0]
// CSSStyleRule
// CSSFontFaceRule
// CSSKeyframesRule
// など
document.styleSheets[0].cssRules[0].style
// CSSStyleDeclaration
document.body.style
// CSSStyleDeclaration
document.styleSheets にその document 内でロードされている stylesheet のリストが格納されています
これが StyleSheetList で配列みたいなものです
中には StyleSheet を継承した CSSStyleSheet があります
CSSStyleSheet には cssRules プロパティがあり これが CSSRuleList です
これも配列みたいなものです
CSSRuleList のそれぞれは CSSRule を継承したもので CSSStyleRule や CSSKeyframesRule などがあります
CSSStyleRule の style プロパティが CSSStyleDeclaration です
element の style プロパティも同じく CSSStyleDeclaration です
CSSStyleDeclaration
スタイル書き換えの基本になるオブジェクトが CSSStyleDeclaration ですよくある JavaScript でスタイルを書き換える処理
document.body.style.color = "red"
は CSSStyleDeclaration のプロパティ color を書き換えています
CSSStyleDeclaration には全部の CSS のプロパティが存在します
document.body.style.margin = 0
document.body.style.fontSize = "12px"
console.log(document.body.style)
document.body.style.fontSize = "12px"
console.log(document.body.style)
CSSStyleDeclaration {0: "margin-top", 1: "margin-right", 2: "margin-bottom"…}
0: "margin-top"
1: "margin-right"
2: "margin-bottom"
3: "margin-left"
4: "font-size"
alignContent: ""
alignItems: ""
alignSelf: ""
alignmentBaseline: ""
all: ""
animation: ""
animationDelay: ""
animationDirection: ""
animationDuration: ""
animationFillMode: ""
animationIterationCount: ""
animationName: ""
animationPlayState: ""
animationTimingFunction: ""
backfaceVisibility: ""
(略)
cssFloat: ""
cssText: "margin: 0px; font-size: 12px;"
cursor: ""
(略)
fontKerning: ""
fontSize: "12px"
fontStretch: ""
(略)
left: ""
length: 5
letterSpacing: ""
(略)
listStyleType: ""
margin: "0px"
marginBottom: "0px"
marginLeft: "0px"
marginRight: "0px"
marginTop: "0px"
marker: ""
(略)
0: "margin-top"
1: "margin-right"
2: "margin-bottom"
3: "margin-left"
4: "font-size"
alignContent: ""
alignItems: ""
alignSelf: ""
alignmentBaseline: ""
all: ""
animation: ""
animationDelay: ""
animationDirection: ""
animationDuration: ""
animationFillMode: ""
animationIterationCount: ""
animationName: ""
animationPlayState: ""
animationTimingFunction: ""
backfaceVisibility: ""
(略)
cssFloat: ""
cssText: "margin: 0px; font-size: 12px;"
cursor: ""
(略)
fontKerning: ""
fontSize: "12px"
fontStretch: ""
(略)
left: ""
length: 5
letterSpacing: ""
(略)
listStyleType: ""
margin: "0px"
marginBottom: "0px"
marginLeft: "0px"
marginRight: "0px"
marginTop: "0px"
marker: ""
(略)
未指定のものは空文字で指定したものにはその値が入っています
また cssText には {} の内側に書くルール全体のテキストが入っています
値が入ってるものをわかるようにするためか配列のような機能もあり
length プロパティに設定済みの数
0 から始まる数字のキーには設定済みの key 名が入っています
ここは入れた順(ルール順)になってるようです
style 自体にルールの文字列を代入することもできます
document.body.style = "max-width: 1000px !important"
console.log(document.body.style)
console.log(document.body.style)
CSSStyleDeclaration {0: "margin-top", alignContent: "", alignItems: ""…}
0: "max-width"
alignContent: ""
alignItems: ""
alignSelf: ""
(略)
cssText: "max-width: 1000px !important;"
(略)
margin: ""
marginBottom: ""
marginLeft: ""
marginRight: ""
marginTop: ""
(略)
maxWidth: "1000px"
(略)
0: "max-width"
alignContent: ""
alignItems: ""
alignSelf: ""
(略)
cssText: "max-width: 1000px !important;"
(略)
margin: ""
marginBottom: ""
marginLeft: ""
marginRight: ""
marginTop: ""
(略)
maxWidth: "1000px"
(略)
そうすると追加ではなく完全にリセットされて指定したプロパティだけの設定になります
配列要素は今回の max-width だけになっていて margin などは空文字にもどっています
メソッド
important をつけたのに maxWidth のところには表示されていませんimportant を確認するには getPropertyPriority を使います
document.body.style.getPropertyPriority("max-width")
// "important"
document.body.style.getPropertyPriority("color")
// ""
// "important"
document.body.style.getPropertyPriority("color")
// ""
important なら important の文字がでます
important でないなら空文字です
他には getPropertyValue, removeProperty, setProperty メソッドがあります
document.body.style.setProperty("color", "red", "important")
document.body.style.setProperty("box-sizing", "border-box")
document.body.style.getPropertyValue("color")
// "red"
document.body.style.getPropertyValue("box-sizing")
// "border-box"
document.body.style.getPropertyPriority("color")
// "important"
document.body.style.getPropertyPriority("box-sizing")
// ""
document.body.style.removeProperty("color")
// "red"
document.body.style.setProperty("box-sizing", "border-box")
document.body.style.getPropertyValue("color")
// "red"
document.body.style.getPropertyValue("box-sizing")
// "border-box"
document.body.style.getPropertyPriority("color")
// "important"
document.body.style.getPropertyPriority("box-sizing")
// ""
document.body.style.removeProperty("color")
// "red"
動きは名前通りです
setProerty は第三引数に "important" をつけると important にできます
removeProperty は一応返り値で削除したときの値が受け取れます
CSSStyleDeclaration は stylesheet のひとつひとつのルールに存在します
CSSRule
link でインポートしたり style タグで書いた stylesheet の中のa{color: #88f;}
などのひとつひとつのルールが CSSRule になります
↑みたいな基本的なもの以外にも
@supports (display: flex) {
.flex { display: flex; }
}
@keyframes anime {
0% { top: 0; }
100% { top: 100%; }
}
@page {
size: A4;
margin: 15mm;
}
@media screen and (min-width:1280px) {
.side { display: block; }
}
@font-face {
font-family: "font-name";
src: url("http://server/font/url");
}
.flex { display: flex; }
}
@keyframes anime {
0% { top: 0; }
100% { top: 100%; }
}
@page {
size: A4;
margin: 15mm;
}
@media screen and (min-width:1280px) {
.side { display: block; }
}
@font-face {
font-family: "font-name";
src: url("http://server/font/url");
}
こういう @ から始まる特殊なものがあります
@page は CSSPageRule で @media は CSSMediaRule のように対応しています
CSSStyleRule
ルールの基本になるのは CSSStyleRule ですこれは
body{font-family: meiryo;}
のような普通のひとつのルールのことです
ひとつの {} が一つのルールなので
html, body{
font-family: meiryo;
margin: 0;
}
font-family: meiryo;
margin: 0;
}
こんなにセレクタが複数になっても中が font-family と margin の 2 つになってもひとつの CSSStyleRule です
CSSStyleRule の style プロパティが上で書いた CSSStyleDeclaration です
cssText プロパティにはセレクタも入ったルールのテキストが入っています
selectorText にはセレクタ部分だけが入っています
CSSRule の中のどのタイプのルールなのかは type プロパティに数字で入っています
JavaScript では珍しめな enum 形式です
他の CSSRule
他の CSSRule の場合は 中に CSSRuleList や CSSStyleSheet をもっていたりしますmedia や supports は条件に合っていた時に処理される CSSRuleList を cssRules プロパティとしてもっています
import だと stylesheet 自体をインポートなので styleSheet プロパティに CSSStyleSheet をもっています
keyframes だと内側には 0% のような特殊なセレクタをもつ CSSStyleRule なので CSSKeyframesRule のプロパティ cssRules には CSSStyleRule の代わりに CSSKeyframeRule (s がない単体版)をもっています
CSSKeyframeRule は CSSStyleRule と似たもので同じように style プロパティに CSSStyleDelaration があります
font-face では src などは CSSStyleDeclaration として表現されています
構造のサンプル
CSSStyleSheet
cssRules: CSSRuleList
0: CSSKeyframesRule
cssRules: CSSRuleList
0: CSSKeyframeRule
cssText: "0% { transform: rotateX(0deg); }"
keyText: "0%"
(略)
style: CSSStyleDeclaration
(略)
1: CSSKeyframeRule
(略)
keyText: "20%"
(略)
2: CSSKeyframeRule
cssText: "@keyframes anime { 0% (略)"
name: "anime"
1: CSSStyleRule
CSSStyleSheet
cssRules: CSSRuleList
0: CSSMediaRule
cssRules: CSSRuleList
0: CSSStyleRule
cssText: "@media screen and (max-width: 1280px) { html { (略)"
media: MediaList
0: "screen and (max-width: 1280px)"
length: 1
mediaText: "screen and (max-width: 1280px)"
name: "anime"
1: CSSStyleRule
cssRules: CSSRuleList
0: CSSKeyframesRule
cssRules: CSSRuleList
0: CSSKeyframeRule
cssText: "0% { transform: rotateX(0deg); }"
keyText: "0%"
(略)
style: CSSStyleDeclaration
(略)
1: CSSKeyframeRule
(略)
keyText: "20%"
(略)
2: CSSKeyframeRule
cssText: "@keyframes anime { 0% (略)"
name: "anime"
1: CSSStyleRule
CSSStyleSheet
cssRules: CSSRuleList
0: CSSMediaRule
cssRules: CSSRuleList
0: CSSStyleRule
cssText: "@media screen and (max-width: 1280px) { html { (略)"
media: MediaList
0: "screen and (max-width: 1280px)"
length: 1
mediaText: "screen and (max-width: 1280px)"
name: "anime"
1: CSSStyleRule
StyleSheet
使われているのはどれも StyleSheet を継承した CSSStyleSheet ですdocument.styleSheets に document がロードした CSSStyleSheet がリストになった StyleSheetList があります
style/link タグとの関係
ひとつの CSSStyleSheet がひとつの style または link の Elementに対応していますhref プロパティを見ることで style タグを使ってインラインで書いたか URL からロードしたかわかります
style タグなら href が null になっています
ownerNode プロパティが link または style タグの要素への参照になっています
反対に 要素から CSSStyleSheet を取得したいときは sheet プロパティを使います
document.querySelector("style#css").sheet
// CSSStyleSheet { ... }
// CSSStyleSheet { ... }
クロスオリジン制約
CSSStyleSheet の cssRules プロパティからその stylesheet の CSSRuleList が見れますただし クロスオリジン制約を受けるので 別オリジンからロードしたものの cssRules は null になっていて参照できません
メソッド
ルールの追加削除をするメソッドがあります追加は insertRule 削除は deleteRule です
document.styleSheets[0].insertRule("body{background:black}", 0)
document.styleSheets[0].deleteRule(0)
document.styleSheets[0].deleteRule(0)
追加する場所やどのルールを消すかの指定には index を使います
互換性のためか古いプロパティも残ってるみたいです
追加: addRule ▶ insertRule
削除: removeRule ▶ deleteRule
ルールリスト: rules ▶ cssRules
個人的には古いの名前のほうが好きです
disabled
CSSStyleSheet の単位で無効化が可能ですdisabled プロパティがあるのでそれを true にすれば無効になります
link タグでロードしてる場合は link の属性に disabled をつけることもできます
style タグの場合は disabled 属性は効果なかったです
また ロード中に disabled の切り替えをすると変に混ざったような状態になるので onload 後に操作するのが無難です
disabled の付け替えを使えば テーマをつくるときなどに楽になります
body にテーマのクラスをつけることで切り替えて CSS 側は
body.darktheme li {}
body.darktheme a {}
body.darktheme a {}
のようにすべてのセレクタに テーマの名前のプレフィックスをつけるものは多い気がしますが disabled を JavaScript で切り替えればプレフィックスは無くても大丈夫です
<link rel="stylesheet" class="theme" id="darktheme" href="" />
<link rel="stylesheet" class="theme" id="lighttheme" href="" />
<link rel="stylesheet" class="theme" id="lighttheme" href="" />
例えばこんな感じで複数のテーマの CSS をロードしていたときに
function useTheme(name){
;[...document.querySelectorAll("link.theme")].forEach(e => e.sheet.disabled = e.id !== name)
}
;[...document.querySelectorAll("link.theme")].forEach(e => e.sheet.disabled = e.id !== name)
}
こうやって指定のテーマ以外を disabled にすればいいです
CSS
とりあえずこれで一通りの CSS 関係のオブジェクトの紹介が終わったのですが どこにも使われていない 「CSS」 がありますnew してみても Illegal constructor と言われますし インスタンスを作れないものなのかもしれません
使いみちですが CSS には escape と supports 関数がプロパティとして用意されています
supports
css の @supports みたいにサポートされてるかを JavaScript 上でチェックできますCSS.supports("display", "flex")
// true
CSS.supports("display", "grid")
// false
// true
CSS.supports("display", "grid")
// false
escape
CSS セレクタ用のエスケープをしてくれます記号をつけたクラスをつけます
var class_name = "a#b.c=d+e*f@g!h"
document.body.className = class_name
document.querySelector("." + CSS.escape(class_name))
document.body.className = class_name
document.querySelector("." + CSS.escape(class_name))
<body class="a#b.c=d+e*f@g!h">…</body>
エスケープしないと invalid なセレクタですと怒られてしまいます
Math みたいなプロパティしか使わないなら CSS 自体がオブジェクトでいいのに関数ということはどこかにインスタンスありそうな気もしますけど見つからないんですよね
まとめ
このレベルの CSS 操作をする必要があるのかわかりませんがいつか役立つのかもしれません要素の style プロパティに書くのは 避けたい(jQuery が DOM を荒らしていくような感じがする)ので出来る限り stylesheet の方でどうにかしたいです
でも普段からここまで細かくルール単位で制御するのは大変です
やっぱり 基本は class の書き換えだけで対処できるようにして 大量の要素のクラスを書き換えるような スタイルの方を変えたほうが楽な場合だけルールを書き換えるくらいがよさそうです
スタイル書き換えにしても書き換えそうなところだけ別のタグにしておいて innerHTML を直接書き換えた方が簡単でいいのかも
なので知ってて役立つ可能性が大きいのは disabled プロパティかと思います