見出し画像

(5) スタイルの適用と閉じ込め - デザイナー向けのVue.js紹介

「HTML/CSSはわかるけれど、JavaScriptは少し読んだことがあるくらい」というデザイナー向けの、Vue.js紹介記事です。
この記事では、コンポーネントにスタイルを適用し、スタイルを閉じ込める方法や共通CSSを使う方法を解説します。
連載をまとめたマガジンはこちら↓

スタイルの適用

前回までに作った新しいコンポーネントにスタイルを適用してみましょう。今回は下のように、端が透明にグラデーションする、文字色と同じ線にしてみました。(BaseLine.vueの<style>タグ内)

画像1

<style>
hr {
 box-sizing: border-box;
 border: none;
 height: 1px;
 background: linear-gradient(
   to right,
   transparent 0%,
   #2c3e50 10%,
   #2c3e50 90%,
   transparent 100%
 );
 opacity: 0.5;
}
</style>

このスタイルを適用したサンドボックスはこちら

どのようなCSSがビルドされるか確認する
前々回で解説した通り、Vueのコンポーネントファイルは最終的にHTML/CSS/JavaScriptにビルドされます。実際にどのようなCSSにビルドされているのか、ブラウザの開発者ツールを使用して確認してみましょう。
(プレビューパネルでは見にくいので、プレビューパネル右上のOpen In New Windowから新しい画面でプレビューを開くといいでしょう)

画像2

上の例はGoogle Chromeでの画像です。<style>タグがいくつかありますが、その中にBaseLine.vueの<style>タグに書いた内容がそのまま出力されている部分があるのがわかります。

問題点 - 影響範囲が広すぎる

このシンプルな方法だと、<style>の指定はページ内全体に影響します。ただのCSSなので、BaseLineコンポーネントの中の<hr>かどうかに関係なく、ページ内の<hr>タグ全部にこのスタイルが適用されてしまいます。
試しに<BaseLine/>ではないただの<hr>をApp.vueに追加しても、スタイルが適用されてしまっているのがわかります。

画像3

HTML/CSSでは、この問題を解決するためにクラス名を使ってスタイルを指定しますね。
Vueでは、コンポーネントにスタイルを閉じ込める方法がいくつか用意されています。

スタイルの閉じ込め① スコープ付きCSS(scoped)

まず紹介するのはスコープ付きCSSと呼ばれる方法で、とても簡単に利用できます。Vueコンポーネントの<style>タグにscoped属性を追加するだけです。

画像4

BaseLine.vueのstyleタグにscoped属性を追加すると、BaseLineコンポーネント以外の<hr>タグにはスタイルが適用されなくなります。
(上手くいかないときはこちらのサンドボックスと見比べてください)

画像5

ビルドされたHTML/CSSの確認
どうしてこれだけの記述で、スタイルがBaseLineコンポーネントに限定されるのでしょうか? 先ほどと同様に、ブラウザの開発者ツールでビルドされたHTML/CSSを確認してみましょう。

画像6

<style>タグ内のセレクタに、[data-v-xxxxx]という属性セレクタが追加されていますね。さらに、BaseLineコンポーネント内の<hr>にdata-v-xxxxxという属性値が設定されています。
(xxxxxの部分には他と重複しない文字列が自動で割り当てられるので、ご自身で確認した値と画像内の値は異なるかもしれません。)

HTMLのdata属性は、ユーザーが自由に値を設定できる属性でしたね。そこに目印となる値をセットすることで、CSSを適用したい要素だけを振り分ける仕組みです。
そしてそれを実現するために、scopedが付いている<style>タグの場合はビルド時に自動でCSSとHTMLに属性を付与しているのです。

スコープ付きCSS(scoped)の仕組み
①<style>タグにscopedがついている場合、ビルド時に以下の処理を行う
②<template>タグ内の要素にdata属性を設定
③<style>タグ内のセレクタに属性セレクタを追加
→属性のついていない要素にはCSSが適用されなくなる!

スコープ付きCSS(scoped)の注意点
ただし、一般的に多くのブラウザで属性セレクタはクラスなどのセレクタよりパフォーマンスが低いため、今回の例のようにタグ名+属性セレクタの形に展開される使い方は、あまりよくありません。
実際に使うときは、classと一緒に使うようにしましょう。今回のスタイルも、以下のように修正しておきます。(修正したサンドボックスはこちら

画像7

ヒント
重複しない属性値が自動で追加されるため、クラス名を重複しない長い名前にする必要はありません

スタイルの閉じ込め② CSS Modules(module)

もうひとつの方法はCSS Modulesと呼ばれる方法です。
この方法を有効にするには、2ステップの手順が必要です。まず、scopedの時と同様に、<style>タグにmodule属性を追加します。

画像8

次に、スタイルを適用したい<template>内のclassを修正します。

画像9

・class ではなく :class であることに注意
・クラス名の前に $style. をつけていることに注意

これで、scopedのときと同様、<BaseLine>以外の<hr>にはスタイルが適用されなくなります。(上手くいかないときはこのサンドボックスと見比べてください)

ビルドされたHTML/CSSの確認
scopedのときと同様に、ブラウザの開発者ツールでビルドされたHTML/CSSを確認してみましょう。

画像10

<style>内のセレクタが長い名前に変更されていますね。また、<template>内で指定していたclass名も変更されています。

scopedの時は属性値と属性セレクタを追加することで行っていた要素の振り分けを、重複しない長いクラス名を自動生成することで行うのが、CSS Modulesの動作です。

CSS Modules(module)の仕組み
①<style>タグにmoduleがついている場合、以下の処理を行う
②<style>タグ内のクラス名を重複しない長い名前に変換
③<template>タグ内の$style.クラス名の部分に変換された長い名前が展開される
→クラスのついていない要素にはCSSが適用されなくなる!

(おまけ)「:class」の「:」とか「$style」とかは何?
CSS Modulesを使用する際の<template>内の記法について理解するためには、まだ解説していない「v-bind」機能の理解が必要になります。解説が長くなりすぎてしまうので、現段階では「変換された長いクラス名を展開するための記法」だととらえていてください。

CSS Modules(module)の注意点
CSS Modulesでは最終的なHTML/CSSがクラスセレクタのみになるので、属性セレクタを使うscopedよりパフォーマンスが高くなります。
しかしその反面、<template>への記述は少しだけ複雑になります。

どちらを使うか?

scopedとmoduleのどちらを使うべきかは、2つの仕組みの違いとビルドされるHTML/CSSを考慮して決めましょう。
個人的には、書くときには多少面倒でも動作時にパフォーマンスの良いmoduleの方をよく使います。

共通CSSとの共存

ここまで、scopeやmoduleを使ってスタイルをコンポーネントに閉じ込める方法を解説しました。
しかし、コンポーネント内だけに限定したいスタイルとは逆に、色やborder-radiusなど、サイト全体で統一したいスタイルも存在しますよね。
このような場合には、今まで通りの共通CSSファイルのスタイルもそのまま使用できます。

今まで作ってきたサンプルでは、<BaseLine>の線の色はAppコンポーネントで指定された文字色と同じにしていましたが、実務では文字色は共通CSSファイルに切り出しているかと思います。サンプルでも同じことをしてみましょう。

共通CSSファイルの作成
今回は「すでに共通CSSが存在している」ような想定として、
①トップページのindex.htmlと同じフォルダにcss/default.cssを作り
②CSSのカスタムプロパティ機能で色を定義
してみます。

画像11

「--fg-color」というカスタムプロパティに、文字や線に使う色を定義しました。

共通CSSファイルの読み込み
このままでは、css/default.cssがindex.htmlに読み込まれません。
ビルド作業で自動生成される内容にdefault.cssが含まれるように設定する方法もありますが、今回は「すでに共通CSSが存在して使われている」前提とするため、index.htmlに普通に<link rel="...">で読み込みましょう。

画像12

共通CSS内に定義されたカスタムプロパティを使用
これでdefault.cssの内容が読み込まれるようになったので、BaseLineコンポーネントの線の色を指定する部分をカスタムプロパティで書き直しましょう。

画像13

上手くいかない場合は、このサンドボックスと見比べてみてください。

今回のまとめ

・コンポーネントの<style>に書いた内容は、そのままページの<style>として出力される
・それだけだとCSSの影響範囲が広すぎるので、スタイルをコンポーネント内に閉じ込める(コンポーネント以外に影響を出さない)方法が用意されている
<style scoped>を使うと、data属性の自動付与を使用したスタイルの閉じ込めができる
<style module>を使うと、重複しない長いクラス名への自動変換を使用したスタイルの閉じ込めができる
スタイル閉じ込めと共通CSSは共存できる

-----------------------------

ここまでお読みいただきありがとうございました。次回からは、子要素を持つ複雑なコンポーネントを作る方法を解説していきます。

-----------------------------

おまけ:練習課題

スタイルの閉じ込め方がわかったので、scopedとmoduleそれぞれの記法を試してみましょう。

・HelloWorldコンポーネントは、スタイルの指定にタグ名+scopedを使用しています。パフォーマンス向上のため、クラス名+scopedを使うように書き換えてみましょう。どのようなHTML/CSSが生成されるか確認しましょう。
・HelloWorldコンポーネントをmoduleを使用して書き換えてみましょう。どのようなHTML/CSSが生成されるか確認しましょう。
・Appコンポーネントのスタイルから、ページ全体で共通するスタイル(フォントファミリーなど)をdefault.cssに移動してみましょう。

🐥はげみになります!