Skip to content

Instantly share code, notes, and snippets.

@tak-dcxi
Last active June 3, 2025 04:19
Show Gist options
  • Select an option

  • Save tak-dcxi/8672786635ea2c08a96359ad1fec833a to your computer and use it in GitHub Desktop.

Select an option

Save tak-dcxi/8672786635ea2c08a96359ad1fec833a to your computer and use it in GitHub Desktop.
ビューポート単位に関するユーティリティ変数およびStylelint設定
module.exports = {
rules: {
'unit-disallowed-list': [
[
'vw',
'vh',
'vi',
'vb',
'vmin',
'vmax'
],
{
message: (unit) => {
const recommendationMap = {
vw: 'svw, dvw, lvw',
vh: 'svh, dvh, lvh',
vi: 'svi, dvi, lvi',
vb: 'svb, dvb, lvb',
vmin: 'svmin, dvmin, lvmin',
vmax: 'svmax, dvmax, lvmax'
}
return `\`${unit}\`は使用しないでください。代わりに\`${recommendationMap[unit]}\`を検討してください。`
},
severity: 'warning'
}
],
},
ignoreFiles: ['**/node_modules/**']
}

ビューポート単位に関するユーティリティ変数およびStylelint設定

このプロジェクトでは、リキッドレイアウトの基準となるアートボード幅の定義と、ビューポート単位の課題を解決するためのカスタムプロパティ、および特定のビューポート単位の使用を制限するStylelintの設定を提供します。

1. アートボード幅の定義 (Art Board Width)

リキッドレイアウトを実装する際の基準となる、デザインカンプのアートボードの最小幅と最大幅をCSSカスタムプロパティとして定義します。

/* ======================================================
// MARK: Art Board Width
//
// リキッドレイアウトの基準となるデザインカンプのアートボードの幅を指定します。
// ====================================================== */
@property --art-board-width-min {
  syntax: '<length>';
  inherits: false;
  initial-value: 375px;
}

@property --art-board-width-max {
  syntax: '<length>';
  inherits: false;
  initial-value: 1440px;
}
  • --art-board-width-min: デザインが対応する最小の幅 (初期値: 375px)
  • --art-board-width-max: デザインが対応する最大の幅 (初期値: 1440px)

これらの値は、要素のサイズやマージンなどを可変にする際の計算基準として利用します。


2. ビューポートインラインサイズ (Viewport Inline Size)

ビューポート単位 svi, dvi, lvi をより安全かつ柔軟に利用するためのカスタムプロパティです。

2.1. ビューポート単位の型定義

100svi, 100dvi, 100lvi といったビューポートのフルサイズを表す値を @property を用いて <length> 型として定義します。これは、tan(atan2()) を使用した計算を異なる単位で組み合わせる際に、型が明確でないと正しく機能しないためです。

/* ======================================================
// MARK: Viewport Inline Size
//
// `tan(atan2())` は `@property` で `length型` で定義しないと異なる単位で組み合わせた際に機能しないため、`100svi`, `100dvi`, `100lvi` は型付けを行います。
// ====================================================== */
@property --svi-full {
  syntax: '<length>';
  initial-value: 0; /* :rootで実際の値が設定される */
  inherits: false;
}

@property --dvi-full {
  syntax: '<length>';
  initial-value: 0; /* :rootで実際の値が設定される */
  inherits: false;
}

@property --lvi-full {
  syntax: '<length>';
  initial-value: 0; /* :rootで実際の値が設定される */
  inherits: false;
}

2.2. ビューポート単位変換ユーティリティ

svi, dvi, lvi 単位を直接使用すると、Chrome 系ブラウザでズーム機能が正しく動作しない問題が報告されています。

この問題を回避するため、三角関数 (tan(atan2())) を利用してこれらのビューポート単位を px 値に変換するカスタムプロパティを提供します。

これにより、任意の数値をビューポート単位相当の px 値として扱うことができ、ズーム機能が正しく動作します。

:root {
  /* ------------------------------------------------------
  // `calc`関数で number を `svi` `dvi` `lvi` に変換するためのカスタムプロパティ。
  // `svi` `dvi` `lvi` をそのまま使用すると Chrome 系ブラウザのズーム機能が効かなくなるため、三角関数を使用して `px` 値に変換します。
  //
  // @use inline-size: calc(240 * var(--to-svi));
  //
  */
  --svi-full: 100svi;
  --to-svi-min: calc(tan(atan2(var(--svi-full), var(--art-board-width-min))) * 1px);
  --to-svi-max: calc(tan(atan2(var(--svi-full), var(--art-board-width-max))) * 1px);
  
  --dvi-full: 100dvi;
  --to-dvi-min: calc(tan(atan2(var(--dvi-full), var(--art-board-width-min))) * 1px);
  --to-dvi-max: calc(tan(atan2(var(--dvi-full), var(--art-board-width-max))) * 1px);
  
  --lvi-full: 100lvi;
  --to-lvi-min: calc(tan(atan2(var(--lvi-full), var(--art-board-width-min))) * 1px);
  --to-lvi-max: calc(tan(atan2(var(--lvi-full), var(--art-board-width-max))) * 1px);
}

使用例:

ある数値をSmall Viewport Inline (svi) に基づいたピクセル値に変換したい場合:

.element {
  /* 例えば、240という数値を --art-board-width-min における svi ベースのピクセル値に変換 */
  inline-size: calc(240 * var(--to-svi-min));
}
  • --to-svi-min, --to-dvi-min, --to-lvi-min: それぞれのビューポート単位を、--art-board-width-min を基準とした px に変換するための係数。
  • --to-svi-max, --to-dvi-max, --to-lvi-max: それぞれのビューポート単位を、--art-board-width-max を基準とした px に変換するための係数。

3. Stylelint設定 (Stylelint Configuration)

特定の古いビューポート単位の使用を制限し、新しい動的なビューポート単位の使用を推奨するためのStylelintルールです。

module.exports = {
  rules: {
    'unit-disallowed-list': [
      [
        'vw',
        'vh',
        'vi',
        'vb',
        'vmin',
        'vmax'
      ],
      {
        message: (unit) => {
          const recommendationMap = {
            vw: 'svw, dvw, lvw',
            vh: 'svh, dvh, lvh',
            vi: 'svi, dvi, lvi',
            vb: 'svb, dvb, lvb',
            vmin: 'svmin, dvmin, lvmin',
            vmax: 'svmax, dvmax, lvmax'
          }
          return `\`${unit}\`は使用しないでください。代わりに\`${recommendationMap[unit]}\`を検討してください。`
        },
        severity: 'warning'
      }
    ],
  },
  ignoreFiles: ['**/node_modules/**']
}

3.1. unit-disallowed-list ルール

  • 禁止される単位:
    • vw
    • vh
    • vi
    • vb
    • vmin
    • vmax
  • 推奨される単位:
    • vw の代わりに svw, dvw, lvw
    • vh の代わりに svh, dvh, lvh
    • vi の代わりに svi, dvi, lvi
    • vb の代わりに svb, dvb, lvb
    • vmin の代わりに svmin, dvmin, lvmin
    • vmax の代わりに svmax, dvmax, lvmax
  • 重要度: warning (警告)
    • これらの単位が検出された場合、Stylelintは警告を出力しますが、ビルドプロセスを停止させることはありません。
  • 無視するファイル:
    • **/node_modules/** 以下のファイルは、このルールのチェック対象外となります。

この設定により、より予測可能で安定したレイアウトを実現するために、新しい動的ビューポート単位への移行を促します。

4. vwvh の使用を禁止する理由

  • vwvh は環境によって表示に差があるから。
  • 接頭辞が無い vwvh は互換性のために残されているという雰囲気がひしひしと仕様の文から伝わってくるから。
  • vhvw はモバイルブラウザのツールバーの影響で誤動作することがある。常に dv-, sv-, lv- を使い分ける意識をしておきたいから。
    • 例えば vh は暗黙的に lvh とみなされるという定義になっている。故にモバイル端末でアドレスバーが出現している場合に見切れる可能性がある。

5. 【おまけ】 コンテナクエリスニペット

このスニペットは、コンテナクエリを利用するための基本的なCSSカスタムプロパティとコンテナコンテキスト定義、およびデバッグ用のスタイルを迅速にセットアップするためのものです。

スニペット名: container

生成されるコード:

--_container-size-min: $1;
--_container-size-max: $2;
--_to-cqi-min: calc(tan(atan2(1px, var(--_container-size-min))) * 100cqi);
--_to-cqi-max: calc(tan(atan2(1px, var(--_container-size-max))) * 100cqi);
container: $3 / inline-size;
@container style(--debug: true) {
  overflow-inline: auto;
  resize: inline;
}

5.1. 目的

特定のコンテナ要素のサイズに基づいてスタイルを適用するコンテナクエリの実装を支援します。 このスニペットは、コンテナのベースの最小/最大サイズを定義し、それに基づいた cqi (container query inline-size) 単位へ変換するカスタムプロパティを生成します。また、コンテナの命名と、デバッグ時に役立つ視覚的なフィードバック(リサイズハンドル)を提供します。

5.2. プレースホルダー (入力値)

スニペットを展開する際に、以下の値を入力します。

  • $1: デザインカンプで取得するコンテナの最小幅 (例: 320px)
    • --_container-size-min に設定されます。
  • $2: デザインカンプで取得するコンテナの最大幅 (例: 1024px)
    • --_container-size-max に設定されます。
  • $3: コンテナ名 (例: my-container)
    • container プロパティの値として設定され、この名前でコンテナクエリの対象を指定できるようになります。

5.3. 生成されるCSSカスタムプロパティ

  • --_container-size-min:
    • コンテナが取りうる最小のインラインサイズ(幅)を指定します。プレースホルダー $1 の値が設定されます。
  • --_container-size-max:
    • コンテナが取りうる最大のインラインサイズ(幅)を指定します。プレースホルダー $2 の値が設定されます。
  • --_to-cqi-min:
    • --_container-size-min を基準とした cqi 値に変換するための係数を計算します。
    • 使用例: width: calc(50 * var(--_to-cqi-min)); は、コンテナの最小幅の約50%の幅を指定する意図で使えます。
  • --_to-cqi-max:
    • --_container-size-max を基準として、同様の計算を行います。

--_to-cqi-min および --_to-cqi-max の計算式は、コンテナの幅そのものを 100cqi とみなし、それに対する相対的なスケール値を算出するものです。これにより、コンテナの最小幅または最大幅を基準点として、レスポンシブな値を設定する際に役立ちます。例えば、あるコンポーネントがコンテナの最小幅の時には X px、最大幅の時には Y px といった値を、これらのカスタムプロパティと calc() を使って表現しやすくなります。

5.4. 生成されるCSSルール

  • container: $3 / inline-size;:
    • コンテナクエリのコンテキストを定義します。
    • $3 には指定したコンテナ名が入ります。
    • inline-size は、コンテナのインライン幅に基づいてクエリが評価されることを意味します。
  • @container style(--debug: true) { ... }:
    • デバッグ用のスタイルコンテナクエリ。:root--debug: true; を指定することでアクティブになります。
    • コンテナのインライン方向にリサイズハンドルを表示し、手動でコンテナの幅を変更できるようにします。コンテナクエリの挙動をテストするのに非常に便利です。

使用例:

スニペットを展開し、以下のように値を入力した場合:

  • $1 (最小幅): 300px
  • $2 (最大幅): 800px
  • $3 (コンテナ名): sidebar

生成されるCSS:

--_container-size-min: 300px;
--_container-size-max: 800px;
--_to-cqi-min: calc(tan(atan2(1px, var(--_container-size-min))) * 100cqi);
--_to-cqi-max: calc(tan(atan2(1px, var(--_container-size-max))) * 100cqi);
container: sidebar / inline-size;
@container style(--debug: true) {
  overflow-inline: auto;
  resize: inline;
}
{
"svi-min": {
"prefix": "svi-min",
"body": ["calc($1 * var(--to-svi-min))"]
},
"svi-max": {
"prefix": "svi-max",
"body": ["calc($1 * var(--to-svi-max))"]
},
"dvi-min": {
"prefix": "dvi-min",
"body": ["calc($1 * var(--to-dvi-min))"]
},
"dvi-max": {
"prefix": "dvi-max",
"body": ["calc($1 * var(--to-dvi-max))"]
},
"lvi-min": {
"prefix": "lvi-min",
"body": ["calc($1 * var(--to-lvi-min))"]
},
"lvi-max": {
"prefix": "lvi-max",
"body": ["calc($1 * var(--to-lvi-max))"]
},
"cqi-min": {
"prefix": "cqi-min",
"body": ["calc($1 * var(--_to-cqi-min))"]
},
"cqi-max": {
"prefix": "cqi-max",
"body": ["calc($1 * var(--_to-cqi-max))"]
},
"container": {
"prefix": "container",
"body": [
"--_container-size-min: $1;",
"--_container-size-max: $2;",
"--_to-cqi-min: calc(tan(atan2(1px, var(--_container-size-min))) * 100cqi);",
"--_to-cqi-max: calc(tan(atan2(1px, var(--_container-size-max))) * 100cqi);",
"container: $3 / inline-size;",
"@container style(--debug: true) {",
"\toverflow-inline: auto;",
"\tresize: inline;",
"}"
]
}
}
/* ======================================================
// MARK: Art Board Width
//
// リキッドレイアウトの基準となるデザインカンプのアートボードの幅を指定します。
// ====================================================== */
@property --art-board-width-min {
syntax: '<length>';
inherits: false;
initial-value: 375px;
}
@property --art-board-width-max {
syntax: '<length>';
inherits: false;
initial-value: 1440px;
}
/* ======================================================
// MARK: Viewport Inline Size
//
// `tan(atan2())` は `@property` で `length型` で定義しないと異なる単位で組み合わせた際に機能しないため、`100svi`, `100dvi`, `100lvi` は型付けを行います。
// ====================================================== */
@property --svi-full {
syntax: '<length>';
initial-value: 0;
inherits: false;
}
@property --dvi-full {
syntax: '<length>';
initial-value: 0;
inherits: false;
}
@property --lvi-full {
syntax: '<length>';
initial-value: 0;
inherits: false;
}
:root {
/* ------------------------------------------------------
// `calc`関数で number を `svi` `dvi` `lvi` に変換するためのカスタムプロパティ。
// `svi` `dvi` `lvi` をそのまま使用すると Chrome 系ブラウザのズーム機能が効かなくなるため、三角関数を使用して `px` 値に変換します。
//
// @use inline-size: calc(240 * var(--to-svi);
//
*/
--svi-full: 100svi;
--to-svi-min: calc(tan(atan2(var(--svi-full), 1px)) / tan(atan2(var(--art-board-width-min), 1px)) * 1px);
--to-svi-max: calc(tan(atan2(var(--svi-full), 1px)) / tan(atan2(var(--art-board-width-max), 1px)) * 1px);
--dvi-full: 100dvi;
--to-dvi-min: calc(tan(atan2(var(--dvi-full), 1px)) / tan(atan2(var(--art-board-width-min), 1px)) * 1px);
--to-dvi-max: calc(tan(atan2(var(--dvi-full), 1px)) / tan(atan2(var(--art-board-width-max), 1px)) * 1px);
--lvi-full: 100lvi;
--to-lvi-min: calc(tan(atan2(var(--lvi-full), 1px)) / tan(atan2(var(--art-board-width-min), 1px)) * 1px);
--to-lvi-max: calc(tan(atan2(var(--lvi-full), 1px)) / tan(atan2(var(--art-board-width-max), 1px)) * 1px);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment