コハム

Webクリエイターのコハムが提供する、Web制作に役立つ情報をまとめたブログです。

CSSとJSの変数管理でイライラしてない?CSSのenv()関数をマスターしよう

Why you should use CSS env()

記事は上記記事を意訳したものです。

※当ブログでの翻訳記事は元サイト様に許可を得て掲載しています。


共有のCSSとJavaScript値を最新の状態に保ち、同期させるのに苦労していますか?共有値を更新する際に、一見ランダムなバグに直面していますか?この記事では、近日公開予定のCSS env() 機能とPostCSSプラグインを使用して、CSSとJS間で同じ変数を共有し、さらにそれらを単一のファイルに保存する方法を紹介します。

よくある以下のシナリオに遭遇したことがあるかもしれません:

  • 比較的複雑なレスポンシブデザインを実装する必要がある
  • CSSセレクターと calc() だけではデザインに影響を与えられないため、信頼できる友人であるJavaScriptに頼る
  • 問題を解決するために、JavaScript からCSS値(例:コンテナのサイズ、間隔の値)にアクセスする必要がある
  • ここで選択を迫られます。変数をJavaScriptだけで管理し、インラインスタイルを使用してスタイリングを適用するか?

それとも、変数をCSSとJSファイルの両方で管理するか?

どちらの解決策も満足のいくものではありません。

一方では、インラインスタイルを適用することで、他の方法でスタイルを上書きする機会を失います。特定の宣言を上書きするには、常にインラインスタイルを使用する必要があります。

以下の例では、インラインスタイルがクラス名よりも優先されるため、MyComponent は常に「赤色」で、フォントサイズは2remになります:

.my-component {
  font-size: 1rem;
  color: blue;
}

インラインスタイルが優先されます。

一方、変数を2つの異なるファイルで管理すると、メンテナンスのリスクにさらされます。値を変更する必要がある場合、片方のファイルでしか更新されないリスクがあり、アプリが奇妙な方法で壊れる可能性があります。

しかし、別の方法があります!

CSS環境変数を紹介し、CSSとJS間で変数を共有しながら、値を1か所に保存する方法をお見せしましょう。

問題

最近遭遇したより具体的な問題を紹介します。

Color Explorer というアプリを構築しているとします。このアプリでは、ユーザーが色のリストを見て、拡大表示する色を選択できます。

初期バージョンは以下のようになっています:

見た目は悪くありません。色のリストがあり、小さなスウォッチをクリックすると大きなスウォッチに表示される色が変わります。

しかし、すべての色を見るためにスクロールダウンする必要があるのが気に入りません。1行に制限したいと思います。

最も簡単な解決策は、カラーリストに overflow: hidden を適用することです:

見た目は良くなりましたが、問題があります。

オーバーフローしているスウォッチは視覚的に隠れていますが、キーボードでアクセスできてしまいます:

これは問題です。

この問題を解決するには、JavaScriptの助けが必要です。

カラーリストコンテナを測定し、1行に収まる色スウォッチの数を計算し、他の色スウォッチを非表示にして無効化します。

そのためには、カラーリストコンテナの幅、各色スウォッチの幅(マージンを含む)、およびカラーリストコンテナの1行に収まる色スウォッチの数を知る必要があります。

カラーリストコンテナがレスポンシブであるため、CSSだけでは実現できません:

色スウォッチは固定幅で、現在その幅はCSSで設定されていますが、どのようにしてその情報をJSからアクセスすればよいでしょうか?

解決策 #1:リスクのある方法

実装が最も簡単な解決策は、ColorSwatch の幅変数を JS に保存することです。

値を JS ファイルにコピー&ペーストするだけです:

素晴らしい!動作します!

シンプルな解決策ですが、脆弱です。

あなた自身、またはこのコンポーネントを初めて扱うチームの誰かが ColorSwatch の幅を更新する必要がある場合はどうなるでしょうか?幅の値が1か所でしか更新されないリスクが高く、多くの場合 CSS ファイルだけが更新され、アプリが壊れてしまいます。開発者がアプリが壊れた理由を理解し、修正方法を見つけるのに時間がかかるでしょう。

CSS 環境変数を使用することで、このリスクを回避できます。

解決策 #2:CSS 環境変数

CSS env() の登場です。

MDN では以下のように説明されています:

「CSS の env() 関数は、var() 関数とカスタムプロパティと同様の方法で、ユーザーエージェントで定義された環境変数の値を CSS に挿入するために使用できます。」

基本的に、プロパティ値を使用できる場所ならどこでも使用できるグローバル変数を定義できます。メディアクエリも含まれます。

CSS env() はまだ公式の CSS 仕様の一部ではありませんが、env() の仕様は「Editor's Draft」段階にあり、ブラウザのサポートも十分にあります。すべての詳細が確定次第、CSS 仕様の一部になる予定です。

ドラフト仕様を確認できますが、要約すると:CSS で環境変数にアクセスする方法について合意されていますが、現在のところ定義やロードする方法はありません。

しかし、便利な PostCSS プラグインを使えば、今日から CSS env() を使用できます!

まず、PostCSS と postcss-env-function プラグインをプロジェクトに追加する必要があります。

(create-react-app を使用している場合、PostCSS はすでにインストールされており、postcss-env-function の GitHub ページに特別な設定手順があります。)

また、css-env-variables.js というファイルを作成する必要があります。以下のような内容になります:

module.exports = {
  environmentVariables: {
    '--color-swatch-size': '10rem'
  }
};

これで基本的なセットアップは完了です!以下のように CSS env() を使用できます:

.color-swatch {
  height: env(--color-swatch-size);
  width: env(--color-swatch-size);
}

かなりシンプルですね?

必要な変数を追加でき、インスペクターで見えるのは実際の値だけです。

CSS 変数が JavaScript ファイルで管理されるようになりましたが、各値には単位が含まれているため、あまり役に立ちません。そのため、JS と CSS の変数を2つの別々のファイルで管理する必要があります。技術的には、CSS env 変数をコンポーネントにインポートすることはできます。試してみてください!

使用するには、値から単位を取り除く方法を見つける必要がありますが、それはアプリが心配すべきことでしょうか?

いいえ。

次のセクションで解決策を説明します。

JS env 変数の使用方法

私のチームと私が現在のプロジェクトで使用し始めたパターンは、数値を別の JS ファイルで定義し、css-env-variables.js にインポートして CSS 変数を構築することです。

このようにして、JS 値を他の JS ファイルに直接インポートし、同じ値を CSS 環境変数の構築に使用できます。

module.exports = {
  colorSwatchSize: 160; // px
};

ここには数値のみがあり、単位は常にピクセルです。JS ファイルで扱いやすいためです。これらを他の JS ファイルに直接インポートできます。

import { colorSwatchSize } from 'path/to/your/js-env-variables';

これでだいぶ近づきました!

js-env-variables.js が CommonJS モジュールで、some-other-file.js が ES6 モジュールであることに気づいたかもしれません。なぜ違いがあるのでしょうか?

js-env-variables.js はトランスパイルされないからです。このファイルはビルド時にのみ PostCSS プラグインによって使用されるため、CommonJS モジュールとして記述する必要があります。

そして、以下のように CSS 環境変数ファイルでこれらの値を使用できます:

const { colorSwatchSize } = require('path/to/your/js-env-variables');

module.exports = {
  environmentVariables: {
    '--color-swatch-size': `${colorSwatchSize}px`
  }
};

素晴らしい!これで値を1か所でのみ管理する必要があります。これが真実の源です。

これにより、この変数のインスタンスを更新し忘れるリスクが効果的に排除されます。一度だけ定義されるからです。

もっと良くできます!

さらに改善できます。

上記のセットアップではピクセルを使用していますが、元々 CSS で 'rem' を使用しており、再び使用したいと思います。

JS ファイルにピクセル値を、CSS に rem 値を提供するようにこのシステムをセットアップするには、いくつかの追加手順が必要です:

  1. システムがルートのフォントサイズを知る必要があります
  2. px から rem への変換関数が必要です

上記と同じ方法でルートのフォントサイズを定義できます:

const {
  colorSwatchSize,
  rootFontSize
} = require('path/to/your/js-env-variables');

module.exports = {
  environmentVariables: {
    '--color-swatch-size': `${colorSwatchSize}px`,
    '--root-font-size': `${rootFontSize}px`
  }
};

メイン CSS ファイルで適用することを忘れないでください:

html {
  font-size: env(--root-font-size);
}

これで、px から rem への変換関数を作成し、CSS env 変数ファイルで使用できます:

const {
  colorSwatchSize,
  rootFontSize
} = require('path/to/your/js-env-variables');
const { getPxToRem } = require('path/to/your/px-to-rem');

const pxToRem = getPxToRem(rootFontSize);

module.exports = {
  environmentVariables: {
    '--color-swatch-size': pxToRem(colorSwatchSize),
    '--root-font-size': `${rootFontSize}px`
  }
};

--color-swatch-size 変数が rem 単位になり、ルートのフォントサイズ値を変更すると影響を受けることがわかるはずです。

これは、将来の自分に多くのデバッグ時間を節約させる素晴らしいセットアップです。

ボーナス:TypeScript を使用している場合は?

TypeScript を使用している場合、いくつかの注意点があります。

JS 環境変数と px-to-rem ユーティリティを CSS env 変数ファイルにインポートするのが難しい場合があります。

この問題は、これらのファイルを独自のディレクトリ内の別々のモジュールとしてセットアップし、型定義を含めることで解決できます。

ディレクトリ構造は以下のようになります:

src
|-css-env-variables.js
|-js-env-variables
| |-index.d.ts
| |-index.js
|-px-to-rem
|-index.d.ts
|-index.js

JS ファイルの内容を変更する必要はありません。/js-env-variables に以下のファイルを追加するだけです:

export const colorSwatchSize: number;
export const rootFontSize: number;

そして /px-to-rem に以下を追加します:

export const getPxToRem: (rootFontSize: number) =>(pxValue: number) => string;

js-env-variables.js と px-to-rem.js の名前を index.js に変更し、それぞれのディレクトリに配置することを忘れないでください。これで TypeScript コンパイラは満足するはずです。

これにより、コンパイラはこれらのモジュールが存在することを認識し、その型に関する情報を得ることができます。

Color Explorer の最終版は Netlify でプレイできます。

結論

ここでは、脆弱で DRY でないシステムを、より DRY で堅牢なものに変えました。

CSS と JS 間で変数を共有する必要性を特定し、最も単純な解決策(同じ変数を2つの異なるファイルに保存すること)が変数を変更する必要がある場合に壊れやすいことがわかりました。

これで、CSS と JS 間で任意の変数を共有し、必要に応じて値を更新する単一の場所を持つツールが手に入りました。この解決策は、変数を共有するためのより堅牢な方法を提供し、変数の値が変更されたときに壊れる可能性が大幅に低くなります。

ここでは、単一の共有変数を使用した単純な解決策を示しました。シンプルなアプリでは、セットアップにかなりの労力が必要でしたが、これは単なる例です。より大規模で複雑なプロジェクトで試してみてください。きっと価値があると感じるでしょう。

もう find-and-replace の狩りは必要ありません。要素の高さのインスタンスをどこかで更新し忘れたという気がかりな感覚もありません。そして、少なくともこのように保存する CSS/JS 変数に関連するランダムなバグもありません。

すでにお分かりかと思いますが、必要に応じて CSS と JS の変数を共有するためにこのセットアップを使用することを強くお勧めします。将来の自分がきっと感謝することでしょう。

©コハム