こんにちは、とりかつです。 今回は、VueでDOMを生成してクラスを割り当てようとしたら、スタイルが適用されなくてハマりました。 そこで、ハマリから得られた知見を共有したいと思います。
やろうとしていたこと
SFCで以下のようなことをしようとしていました。
付与するクラスinner_element
のスタイルは、SFC内の<css scoped>
で定義してあります。
mounted()
のタイミングで動的に<span></span>
を生成 1 生成した要素にnewElement
というクラスを付与$refs
の子要素として追加
コードにすると以下のようになります。
mounted() { const el = this.$refs.hoge as HTMLElement const newElement = document.createElement('span') newElement.className = "inner_element" el.append(newElement) }
いざ実行してみると<span>
はしっかり追加されており、class="inner_element
も確認することができました。
しかし、スタイルは適用される気配がありません。
うまくいかない原因はなんでしょうか。
なんでVueでVanilaJS使ってDOMを追加しようとしとるねんってことはあえて考えないようにしましょう。
VueのCSSが読み込まれるタイミング
スタイルが適用されなくて悩んでる時にscoped css
の仕様についてふと思い出しました。
scoped 属性をもつ <style> タグを利用するとき、その CSS は現在のコンポーネントの要素にのみ適用されます。これは Shadow DOM のスタイルのカプセル化に似ています。いくつか注意点がありますが、polyfill は必要ありません。PostCSS を使用して以下を変換することによって実現しています[^1]
同ページには以下のようなスニペットも掲載されていました。(一部抜粋)
example[data-v-f3f3eg9] { color: red; }
要約すると、SFCのスコープ付きCSSのクラス名はコンパイルされて、.inner_element
→.inner_element[data-v-jfoieh]
のようになるといった感じです。
では、CSSがコンパイルされるタイミングはいつでしょうか。 以下はVue.js公式ドキュメントに掲載されていたライフサイクルの図です。[^2]
この図からはSFCテンプレートがコンパイルされるタイミングがcreated()
とbeforeMount()
の間であることが読み取れます。
ですが、CSSがコンパイルされるタイミングは読み取ることができませんでした。
推測ではありますが、この間でCSSがコンパイルされている可能性がありそうです。
ここで気づいたのですが、CSSがコンパイルされるのと同時にコンパイルによって付与されたID的なものがVueによって生成されるテンプレート内の要素にも付与されるようです。 以下のようなSFCを考えます。
<templete> <button class="button"> </templete> <style scoped> .button { /* some style */ } </style>
Vueによってマウントされたとき、.button
は.button[data-v-horekjkd]
となるとします。
このとき、Vueによって生成されるbutton
は次にようになります。
<...> <button data-v-horekjkd class="button"></button> </...>
このようにVueによって生成されたIDはHTMLのタグに反映?されるようです。
実際にインスペクタを見てみるとv-data-.....
がbutton
に付与されていることがわかります。
少し冗長になってしまいました。 ここまでわかったことをまとめます。
- スコープ付きCSSはVueによってコンパイルされ、一意なIDが付与される
- IDはテンプレート内のタグにも付与される
- CSSがコンパイルされるタイミングははっきりとわからない
- テンプレートは
created()
とbeforeMounted()
の間でコンパイルされ、このタイミングでIDが付与される
CSSがコンパイルされるタイミングをはっきりと掴むことができませんでした。
しかし、CSSのコンパイルで付与されるIDがテンプレート内のタグに付与されることと、テンプレートがコンパイルされるタイミングは明らかになりました。
これによりmounted()
で生成した要素にスタイルが付与されなかったことの説明がつきそうです。
CSSのコンパイルされるタイミングは追々調べようと思います。
まとめ
当初、私はmounted()
で<span class="inner_element"></span>
を生成していました。
しかし、この時点でSFCのCSSクラス名はコンパイルされ.inner_element[v-data-hgoejdklgha]
のように変化していました。
また、テンプレートのコンパイルも終了していました。
スタイルを適用するには、クラスに付与されたIDをテンプレートの要素にも付与する必要がありましたが、私の生成したspan
はIDが付与されていませんでした。
これによってスタイルが適用されなかったと考えられます。
おわりに
ここまで、Vueのmounted()
でVanilaJSによって生成した要素にスタイルが適用されない原因について考えてきました。
思い返してみると... Vueでクラス名をバインドする際は、
:class="{classA: true, classB: false}"
みたいに書く必要がありました。 いままで、以下のように書けたらいいのにな。なんだか気持ち悪いなとか思ってました。
$refs.class = "classC"
ですが、テンプレートがコンパイルされるタイミングでIDが付与されることを考慮すると、
あらかじめクラス名をバインドしておいて、vueのdata
等の状態の切り替えで、クラスの付与のアクティブ/パッシブを切り替えに納得が行きました。
[^1] https://vue-loader-v14.vuejs.org/ja/features/scoped-css.html [^2] https://jp.vuejs.org/v2/guide/instance.html