Skip to content
This page has been auto-translated and may contain errors.View in English

カスタムプロパティとモダンCSS

docs.scrimba.com

ブランドカラーの青を選んで、40個のルールに入力し、サイトをデプロイします。1ヶ月後、マーケティング部門がわずかに異なる青を要求します。今、あなたはすべてのスタイルシートを検索し、40個すべてを見つけられることを望み、大文字で綴られた2つを見落とさないように注意する必要があります。カスタムプロパティはこれを修正します。色を1回名前を付け、どこでもその名前を使用し、1つの場所で変更します。この単一の習慣は、テーマング、デザイントークン、リデザインから生き残るスタイルシートの始まりです。

変数の宣言と使用

**カスタムプロパティ**は、再利用するために名前を付けた値です。名前を2つのダッシュの前に書き、値を設定し、後でvar()で読み取ります:

css
:root {
  --color-brand: #2563eb;   /* 値を1回宣言して名前の下に配置 */
}

.button {
  background: var(--color-brand);   /* ここで読み取ります */
}

.link {
  color: var(--color-brand);        /* ここでも、同じ値 */
}

--color-brandは名前(2つのダッシュは必須)、var(--color-brand)がその使用方法です。:root(これは<html>要素で、ページの最上部)で宣言すると、その名前はどこからでも利用可能になります。:rootの1行を変更すると、それを読み取るすべてのルールが更新されます。

**カスタムプロパティ**は、--プリフィックスで宣言し、var()関数で読み取られる名前付きの値です。人々はこれをCSS変数とも呼び、2つの名前は同じ意味です:

css
:root {
  --color-brand: #2563eb;
  --color-brand-dark: #1e40af;
}

.button {
  background: var(--color-brand);
}

.button:hover {
  background: var(--color-brand-dark);
}

--プリフィックスは、カスタムプロパティを通常のプロパティから区別するものであり、大文字と小文字を区別するため、--color-Brand--color-brandは2つの異なる名前です。:rootで宣言することは一般的な選択肢です。値をツリーの最上部に配置し、すべての要素がそれを見ることができ、グローバルトークンがそこに属しているためです。色に限定されません。プロパティが受け入れるすべての値は、長さ、シャドウ、フォントスタックを含む、カスタムプロパティに置くことができます。

**カスタムプロパティ**は、--で始まる名前を持つユーザー定義のプロパティであり、var()関数を通じて読み取られる値を保持しています。名前は大文字と小文字を区別し、値はほぼそのままの形で保存されます。ブラウザは宣言時にそれに対してほとんど何もしませんが、これは実際に解決される場所を見ると重要になります。

css
:root {
  --color-brand: #2563eb;
  --space-md: 16px;
  --shadow-card: 0 1px 3px rgb(0 0 0 / 0.12);
}

.card {
  padding: var(--space-md);
  box-shadow: var(--shadow-card);
}

:rootで宣言することは慣例であり、要件ではありません。:root<html>要素とhtml型セレクタよりもわずかに高い特異性でマッチし、カスタムプロパティが継承されるため、そこに設定された値はすべての子孫に表示されます。これが保持する精神的なモデルです。グローバルレジストリを作成しているのではなく、ルート要素にプロパティを設定し、ドキュメント全体を継承しています。通常のプロパティが受け入れるものはすべて保存でき、値の断片(色チャネル、無単位の数)も保存し、後で組み立てます。

Juno変数の宣言と使用 カスタムプロパティは名前を持つ値です。--color-brandのように2つのダッシュで書き、var(--color-brand)で読み取ります。:rootに配置して、ページ全体で使用できるようにします。1行を変更すると、その名前を読み取るすべてのルールが一緒に更新されます。
Juno変数の宣言と使用--プリフィックスはカスタムプロパティをマークし、var()はそれを読み取ります。:rootで宣言すると、グローバルトークンをツリーの最上部に配置し、すべての要素がそれに到達できます。名前は大文字と小文字を区別し、プロパティが受け入れるすべての値は1つに存在できます。色だけではありません。
Juno変数の宣言と使用 カスタムプロパティは、設定した--nameで、var()を通じて読み取られ、解決されるまでほぼそのままの形で保存されます。:rootは慣例です。これは<html>要素であり、これらのプロパティが継承されるため、そこの値はすべての子孫に到達します。部分的な値を保存して、後で組み立てることもでき、フォールバックセクションはこれを使い始めます。

カスケードと継承

別の言語で変数を使用したことがあれば、この次の部分は驚きです。カスタムプロパティは固定されていません。他のCSSプロパティと同じルールに従い、つまり、子要素に**継承**され、1つの場所で異なる値を与えることができ、残りを変更する必要がありません。

css
:root {
  --color-text: #1f2937;   /* ページ全体のデフォルト */
}

.callout {
  --color-text: #92400e;   /* 異なる値、.callout内のみ */
  color: var(--color-text);
}

p {
  color: var(--color-text);   /* 通常は濃いグレー、コールアウト内では琥珀色 */
}

同じvar(--color-text)は、ほとんどの段落では濃いグレーを与えますが、コールアウト内では琥珀色になります。コールアウトが独自の値を設定したためです。これがテーマングを可能にし、CSSがどのように機能するかでカスケードを見てきました。

これは、カスタムプロパティがSassなどのCSSプリプロセッサからの変数と異なる場所です。プリプロセッサ変数はブラウザがそれを見る前にコンパイルされるため、静的な検索と置換です。カスタムプロパティは実際のCSSプロパティで、ブラウザに表示されているため、カスケードに従い、**継承**します。

カスタムプロパティの値は、宣言された場所ではなく、それを読み取る要素で検索されます。この単一の事実は、コンポーネントテーマングがなぜ機能するかです:

css
:root {
  --color-accent: #2563eb;   /* デフォルトではどこでも青 */
}

.card--warning {
  --color-accent: #d97706;   /* このコンポーネントサブツリーのオーバーライド */
}

.card__badge {
  background: var(--color-accent);   /* 青、または警告カード内では琥珀色 */
}

バッジは--color-accentがどこから来たかについて知らず、気にしません。それ自体に有効な値を読み取り、任意の祖先がそれを再定義している可能性があります。コンテナーのトークンをオーバーライドすると、それを読み取るすべての子孫がリスキンされ、単一のコンポーネントルールを編集することなく可能です。完全なカスケードと継承ルールはCSSがどのように機能するかに存在します。

カスタムプロパティをすべてのプリプロセッサ変数から分離する動作は、カスケードに参加し、デフォルトで**継承**することです。SassまたはLess変数はビルド時に解決され、ブラウザがファイルをロードする前になくなります。カスタムプロパティは要素の実際のプロパティで、colorまたはfont-sizeを制御する同じカスケードによって解決されます。つまり、var()読み取りを実行している要素でプロパティの計算値に対して検索されます。

css
:root {
  --color-accent: #2563eb;
}

.panel[data-tone="warning"] {
  --color-accent: #d97706;   /* サブツリールートに設定 */
}

.panel__button {
  background: var(--color-accent);   /* 要素ごとに解決され、パネルから継承 */
}

保持する価値のある2つの結果があります。1番目は、継承がテーマングを行います。サブツリールートに--color-accentを設定すると、それを読み取るすべての子孫に伝播するため、コンテナーオーバーライドはそのルールに変更を加えることなくコンポーネントツリー全体をリスキンします。2番目は、特異性はvar()自体ではなく、トークンが設定される場所に適用されます。同じ要素に--color-accentを設定する2つのセレクターがある場合、通常のカスケードは、他のプロパティと同じ方法で勝つものを決定します。継承を停止する必要があるプロパティはinitialでリセットするか、@propertyで非継承として宣言できます。これについては、以下のディープダイブが説明しています。カスケードルールがここで例外なく適用されます。

Junoカスケードと継承 カスタムプロパティは固定されていません。子要素に継承され、残りを変更することなく1つの場所で異なる値を設定できます。そのため、同じvar(--color-text)はほとんどの段落では灰色になり、コールアウト内では琥珀色になります。コンテナーの名前を再定義すると、その内部のすべてが新しい値を取得します。
Junoカスケードと継承 ビルド時に消えるSass変数とは異なり、カスタムプロパティはブラウザに表示され、カスケードに従うため、その値はそれを使用する要素で読み取られます。コンテナーのトークンをオーバーライドすると、すべての子孫はリスキンされ、コンポーネントルールのゼロの編集があります。その読み取り場所の動作がテーマングの背後にある全体的なトリックです。
Junoカスケードと継承 カスタムプロパティは継承してカスケードします。プリプロセッサ変数はブラウザがロードする前に消えるため、決して実行されません。var()は読み取り要素の計算値に対して解決されるため、サブツリールートにトークンを設定すると、継承だけでその下のすべてをテーマングできます。特異性はvar()呼び出し自体ではなく、トークンが設定される場所を制御します。

フォールバックとcalc()

var()は第2の値を取ることができ、カスタムプロパティが設定されなかった場合にのみ使用されます。これは**フォールバック**であり、トークンが不足している場合でもルールが機能し続けることを確認します:

css
.card {
  gap: var(--gap, 1rem);   /* --gapが存在すればそれを使用、そうでなければ1rem */
}

calc()を使用してカスタムプロパティで算数を行うこともできます。基本的な間隔値がある場合、2番目の数を硬くコーディングするのではなく、それから大きい値を作成できます:

css
:root {
  --space-md: 16px;
}

.section {
  padding: calc(var(--space-md) * 2);   /* 32px、基本から導出 */
}

この方法で値を導出するということは、--space-mdへの1つの変更が、その上に構築されたすべてを通じて波及することを意味します。

var()は**フォールバック**として機能する第2の引数を受け入れます。var(--gap, 1rem)--gapが設定されている場合は解決され、設定されていない場合は1remに解決されます。このフォールバックは、特定のサブツリーで定義されていない可能性があるトークンを使用して、コンポーネントがトークンが提供されなかった場所にドロップされたときにも理にかなったレンダリングを行うため、使用する価値があります。

css
.card {
  gap: var(--gap, 1rem);
  padding: var(--space-md, 16px);
}

カスタムプロパティはcalc()で本領を発揮します。これは魔法の数字の山を保存するのではなく、1つの値を別の値から導出する方法です:

css
:root {
  --space-md: 16px;
}

.section {
  padding: calc(var(--space-md) * 2);        /* 32px */
  margin-bottom: calc(var(--space-md) * 3);  /* 48px、1つの情報源 */
}

1つ注意することは、フォールバックはコンマを含む完全な値である可能性があることです。したがって、var(--shadow, 0 1px 2px black)は有効であり、最初のコンマの後のすべてはフォールバックとして扱われます。calc()内のユニットがどのように解決されるかは、サイジングユニットを参照してください。

var()は、第2の引数としてオプションのフォールバックを取ります。var(--gap, 1rem)は、読み取り要素で--gapが設定されていない場合(または保証された無効値に設定されている場合)に1remを生成します。**フォールバック**は最初のコンマの後のすべてをむさぼり食らうため、それ自体がコンマを含む値またはネストされたvar()である可能性があり、デフォルトをチェーンできます。var(--gap, var(--space-md, 1rem))

css
.card {
  gap: var(--gap, var(--space-md, 1rem));   /* チェーンされたフォールバック */
}

:root {
  --space-md: 16px;
}

.section {
  padding: calc(var(--space-md) * 2);   /* 32px、硬くコーディングされていない、導出 */
}

calc()とのペアリングは、カスタムプロパティがスケーリングシステムで自分自身を獲得する場所であり、精度ルールが付属しています。カスタムプロパティは無単位の数を保持でき、calc()の内部にユニットを乗算します:

css
:root {
  --scale: 1.5;   /* 無単位、任意のユニットで乗算できるため */
}

.title {
  font-size: calc(1rem * var(--scale));   /* 1.5rem */
  line-height: calc(var(--scale) + 0.2);  /* 純粋な数値演算 */
}

--scaleをユニットなしで保存する理由は、calc(var(--scale) * 16px)が機能し、一方、--scale: 1.5remを保存してcalc(var(--scale) * 16px)を試行する理由は2つの長さを乗算し、無効です。生の比率を無単位のままにし、使用時にユニットをアタッチします。これらのユニットがピクセルにどのように解決されるかは、サイジングユニットに記載されています。

Junoフォールバックとcalc()var(--gap, 1rem)は、トークンが存在すればそれを使用し、存在しない場合は1remにフォールバックするため、値が不足している場合もルールは機能し続けます。そして、calc()を使用すると、calc(var(--space-md) * 2)のように基本間隔の倍数の値を構築できます。基本を変更すると、その上に構築されたすべてが続きます。
Junoフォールバックとcalc()var()への第2の引数はフォールバックです。var(--gap, 1rem)はトークンが設定されなかったケースをカバーします。カスタムプロパティをcalc()とペアリングして、魔法の数字を硬くコーディングするのではなく値を導出し、1つのトークンがスケール全体を駆動するようにします。フォールバックは最初のコンマの後のすべてを取り込むことに注意してください。これは複数パーツの値に便利です。
Junoフォールバックとcalc()var(--gap, 1rem)は貪欲にフォールバックするため、var(--gap, var(--space-md, 1rem))のようなデフォルトをネストできます。2つの長さを乗算すると機能しないため、calc(var(--scale) * 16px)が機能しながら、生の比率を無単位のままにしておきます。そして、使用時にユニットをアタッチします。これにより、1つのトークンがタイプ全体またはスペーシングスケール全体を駆動することができます。

テーマング

カスタムプロパティはツリーの下の方で再定義できるため、クラスの下でトークンを変更することで暗いテーマを構築でき、すべてのコンポーネントルールは放置できます:

css
:root {
  --color-bg: #ffffff;
  --color-text: #1f2937;
}

.theme-dark {
  --color-bg: #0f172a;      /* 同じ名前、新しい値 */
  --color-text: #e2e8f0;
}

body {
  background: var(--color-bg);
  color: var(--color-text);
}

ページにclass="theme-dark"を追加すると、--color-bgまたは--color-textを読み取るすべてのルールが暗い値に切り替わります。テーマスイッチを作成するためにbodyルールやコンポーネントを変更したことはありません。これは、16進数コードを直接書く代わりにトークンとしてに名前を付けることの見返りです。

テーマングは、カスケードするトークンの直接的な報酬です。色を1回トークンとして定義し、トークン名に対してすべてのコンポーネントを記述し、その後テーマはクラスまたはメディアクエリの下での同じ名前の第2セットの値に過ぎません:

css
:root {
  --color-bg: #ffffff;
  --color-text: #1f2937;
  --color-surface: #f8fafc;
}

.theme-dark {
  --color-bg: #0f172a;
  --color-text: #e2e8f0;
  --color-surface: #1e293b;
}

コンテナーでtheme-darkを切り替えると、その内部のすべてがリスキンされます。コンポーネントはトークン名を読み取り、固定された色ではないため。同じことは、テーマがオペレーティングシステムの設定に従うようにするためのメディアクエリでも機能します:

css
@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #0f172a;
    --color-text: #e2e8f0;
    --color-surface: #1e293b;
  }
}

これを保持する規律は、コンポーネントルールから生の16進数コードを完全に取り出し、代わりに色トークンとして名前を付けることです。これは、トークン命名規則が報われる場所でもあり、CSSを整理することはこれに進みます。

テーマングは、単一の値の再利用を超えてカスタムプロパティが重要である理由であり、テーマはトークンレイヤーのカスケードオーバーライド以上のものではないため機能します。コンポーネントはトークン名を読み取ります。テーマはツリーの高い点でこれらの名前を再定義します。コンポーネントレイヤーのものはテーマが存在することを知りません。

css
:root {
  --color-bg: #ffffff;
  --color-text: #1f2937;
  --color-surface: #f8fafc;
}

.theme-dark {
  --color-bg: #0f172a;
  --color-text: #e2e8f0;
  --color-surface: #1e293b;
}

@media (prefers-color-scheme: dark) {
  :root:not(.theme-light) {
    --color-bg: #0f172a;
    --color-text: #e2e8f0;
    --color-surface: #1e293b;
  }
}

本番環境で保持されるパターンは、2段階のトークンシステムです。最初の段階は生の値を保持し、(--blue-600: #2563eb)第2段階はそれらを参照するセマンティックトークンを保持します。(--color-accent: var(--blue-600))。コンポーネントはセマンティック層のみを読み取るため、テーマはセマンティックトークンを再割り当てし、パレット層はそのままにして、スイッチを小さくして意図を読みやすくします。クラスオーバーライドをprefers-color-schemeメディアクエリとペアリングすることで、上記のように、コンポーネントルールを複製せずにシステムデフォルト+手動トグルを提供できます。これを保守可能にしておく命名規律は、ファイルとトークン組織の残りの部分に属し、生の値自体はです。

カスタムプロパティの高度なビューに属する3つのことがあります。これはポーランドを追加するのではなく、可能性を変更します。

カスタムプロパティは**計算値時**に解決されます。つまり、ブラウザはファイルが解析されたときではなく、スタイルを計算するときに要素ごとにvar()を解決します。これはテーマングがなぜ機能するのかであり、プリプロセッサ変数がなぜできないのかです。値は実際のもので、カスケード、継承、実行時に行われた変更に応答します。

実行時は第2のポイントです。これらは実際のプロパティであるため、JavaScriptはそれらを読み取り、書き込むことができ、トークンを読み取るすべてのルールが瞬時に更新されます:

css
:root {
  --sidebar-width: 280px;   /* JSから実行時に読み取りおよび設定 */
}

スクリプトはdocument.documentElement.style.setProperty('--sidebar-width', '320px')を呼び出すことができ、レイアウトは新しい値に対して再流出します。Sass変数は、ページがロードされる長い前にコンパイルされ、決して実行できないことをオファーします。スクリプトがサブツリーのルート以外の任意の要素にプロパティを設定することで、これもサブツリーにスコープできます。これにより、変更がツリーのその分岐のみに限定されます。

3番目のポイントはプロパティ自体の制御です。@propertyを使用すると、カスタムプロパティをタイプ、初期値、および継承するかどうかで登録できます。これにより、カスタムプロパティのスムーズなアニメーションが可能になります。(プレーンなカスタムプロパティは、ブラウザが型付けされていない値を補間できないため、アニメーション化するとオン/オフジャンプになります)。それとともに、モダン値関数はトークンから自然に読み取られます。clamp(min, preferred, max)は優先値を選ぶが決して境界を離れず、min()は最小値を返し、max()は最大値を返すため、width: min(100%, var(--max-width))はメディアクエリなしでトークンで流動幅を上限に設定します。

Junoテーマング テーマは、class="theme-dark"のようなクラスの下に異なる値を持つ同じトークン名です。class="theme-dark"を設定すると、単一のコンポーネントも編集することなく、それらのトークンを読み取るすべてのルールが切り替わります。そのため、16進数コードを直接各ルールに入力するのではなく、色をトークンとして名前付けします。
Junoテーマング トークンがカスケードするため、テーマはクラスまたはprefers-color-schemeメディアクエリの下での同じ名前の第2セットの値に過ぎません。コンポーネントルールから生の16進数を取り出し、代わりにトークン名を読み取り、サブツリー全体が1つのオーバーライドからリスキンされます。クラスを切り替えるか、OS設定に一致させ、それらのトークンを読み取るすべてが続きます。
Junoテーマング カスタムプロパティは計算値時に解決されるため、それらは実際のものです。JavaScriptは実行時に--sidebar-widthを設定でき、それを読み取るすべてのルールが再流出します。プリプロセッサ変数がすることはできません。2段階セットアップ、セマンティックトークンの下の生のパレットトークンは、テーマスイッチをひと握りの再割り当てに保ちます。そして、@propertyに加えて、clamp()min()max()により、それらのトークンはメディアクエリなしでアニメーション化され、境界内に保持されます。