はじめに
動的に生成される要素のCSSをあらかじめ変更しておきたい。といった稀なケースの対応方法。
先に方法を書いてしまうと
「JavaScriptで生成したstyleタグにCSSを書いて、htmlのheadタグに追加して、動的生成する要素に適用する」原始的な方法です。
稀なケースとは?
例えば「動的生成される要素のCSSを変更したいけど、要素の生成を監視はしない」といったお題があり、以下のような縛りまである場合。
生成する動的要素に対して、
- 新しいclassやidを追加しない
- style属性を使ってCSSを書き込まない
- 外部スタイルシートも修正しない
JavaScriptでよくある要素のCSS変更方法
JavaScriptを使用したCSS変更の多くが、以下のように要素の取得が前提になっています。
// 要素を取得
var div = document.getElementById('target');
// または下記で取得
// var div = document.querySelector('#target');
// 取得要素のCSSを変更
div.style.backgroundColor = 'red';
生成前の要素は取得できずCSSの設定変更はできないので、styleタグ(style要素)を利用することにします。
headタグ内のstyleタグに書いたCSSは、動的要素の出現時にすぐに適用されるので、先にstyleタグを動的生成してしまいます。
styleタグとCSSの追加方法
動的にstyleタグを追加する方法は以下の3ステップと簡単。
// 1. styleタグを生成
var styleTag = document.createElement('style');
// 2. styleタグ内に追加するCSSを記述
styleTag.textContent = '#target {background-color: #f00;}';
// 3. headタグにstyleタグ追加
document.head.appendChild(styleTag);
createElement
でstyleタグを生成してtextContent
にCSSを書いたら、appendChild
でheadタグ内に追加するだけです。
headタグ内に追加されたら、CSSが適用されます。
追加したstyleタグを削除する場合に備えて要素名.id = 'id名';
のようにidを付与しておくと便利です。(例えば、 styleTag.id = 'js_style';)
CSS記述サンプル
var styleTag = document.createElement('style');
document.head.appendChild(styleTag);
// 1個追加
styleTag.textContent = '#target {background-color: #0f0 !important;}';
// まとめて複数追加(テンプレートリテラル使用)
styleTag.textContent = `
#target {fonr-size: 10px;}
#target {border: solid 3px #0f0;}
`;
// 変数とコメントアウトを使う
var s = 20;
styleTag.textContent = `
/* CSS書式のコメントアウトも使用できる */
#target {font-size: ${ s }px;}
#target {border: solid 1px green;}
`;
// += 演算子で追記する
styleTag.textContent += `
#target {padding: 10px;}
`;
CSSと同じ書式セレクタ名 {プロパティ名: 値;}
で書けるところが一目瞭然で良いですね。
ショートハンドで書くこともできます。
テンプレートリテラルを使うと
- 改行できる(スタイルシートからのコピペも出来て便利)
- CSS書式のコメントアウトが使える(/* ~ */)
- 変数を使いたいときは
${ 変数名 }
のように書けばOK
のように使い道が広がります。
サンプル
以下は、動的に生成される要素のCSSをあらかじめ変更するサンプル。
- ボタンを押して1~5個のdivタグを#parent(width300px)内に動的生成
- 動的生成されるdivタグにはクラス(.child)を付与してあり、widthは100pxに設定している
- div.childが#parentに横並びでちょうど300pxに収まるように、動的生成前にwidthを計算して生成したstyleタグに書き込み、上書きする
<!DOCTYPE html>
<html>
<head>
<title>sample</title>
<style>
#parent {
width: 300px;
height: 30px;
background-color: #efefef;
}
.child {
width: 100px;
}
</style>
</head>
<body>
<p>▼ #parent ▼</p>
<div id="parent"></div>
<button id="btn">動的に要素を生成</button>
<script>
var btn = document.getElementById('btn');
btn.addEventListener('click', function(){
// 生成したstyleがあれば削除(リセット)
var styleTag = document.getElementById('js_style');
if(styleTag != null){
styleTag.remove();
}
// 1 ~ 5までランダムな整数を生成
var num = 0;
num = Math.floor(Math.random() * 5) + 1;
// 動的にstyleタグを追加
var styleTag = document.createElement('style'); // styleタグ生成
styleTag.id = 'js_style'; // id付与
document.head.appendChild(styleTag); // headタグに<style id="js_style">を追加
// 動的に追加する要素の幅を計算して、あらかじめCSSに設定
var w = Math.floor(300 / num);
styleTag.textContent = `
/* styleタグを追加 */
#parent div.child {
display: inline-block;
box-sizing: border-box;
width: ${ w }px;
border: solid 1px #000;
}
`;
// 要素を動的に追加(1 ~ 5個)
var target = document.getElementById('parent');
target.innerHTML = '';
for (var i = 0; i < num; i++ ) {
var elem = document.createElement('div');
elem.classList.add('child');
elem.textContent = i + 1 + '個目';
target.appendChild(elem);
}
});
</script>
</body>
</html>
あとがき
「style要素を生成して追加する」といったトリッキーな事をする機会はまず無く、MutationObserver
で要素の生成を監視して、CSSを上書きする方法が正攻法かなと思います。そこを敢えて何とかして別の方法はないか??という試みでしたが、やってみると意外に面白かった。(本来なら設計で稀なケースを回避しておくことが大事かと思います)