Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ソング: ソング側CSSカラー定義が使いづらいのを調整する #2337

Open
romot-co opened this issue Nov 3, 2024 · 6 comments
Assignees

Comments

@romot-co
Copy link
Contributor

romot-co commented Nov 3, 2024

内容

現状、ソング側のCSSカラー定義とその利用が扱いづらくなっている
開発時に扱いやすいように修正する

動的変更やシステムカラーの利用についても考慮する

Pros 良くなる点

  • 開発時に取り扱いやすくなる

Cons 悪くなる点

  • なし

実現方法

特にカラーに対する相対的な状態変更(ex: アクティブであったりホバーであったり)で
Lr値での変更がややこしいため
light/darkそれぞれでlr値の変更を容易にするSASS関数/TS関数を追加する

VOICEVOXのバージョン

0.2.2?

OSの種類/ディストリ/バージョン

その他

@romot-co romot-co self-assigned this Nov 3, 2024
@romot-co
Copy link
Contributor Author

romot-co commented Dec 1, 2024

設計を考える

なるべく使いやすくて変更が容易なものにしたい

責務分割とカプセル化

おおまかに:

  • プリミティブカラー primary/secondaryなど
  • ロールカラー surfaceなど
  • 各コンポーネントカラー sing-ruler-surface / sing-ruler-measure-lineなど

に分割して相互に疎にする
定義と使用を分離する
各コンポーネントカラーは、そのコンポーネント内で定義・管理する

変更の容易性

  • 明度変化の関数の定義(--lrのややこしさを減らす…)
@function lr($base-lr, $adjustment) {
  $new-lr: $base-lr + $adjustment;
  $new-lr: max(0, min($new-lr, 100));
  @return '--lr-#{$new-lr}';
}
  
.class {
  --hover-bg: #{lr($base-lr, 5)};
}

状態の共通化

  • hoverやactiveなどは明度変化の変数定義+ミックスインなどにして各所で利用
// ホバーでの明度変化量
--hover-state-change: 5;

// ホバー変化
@mixin hover-state($base-color, $base-lr) {
  &:hover {
    // ライト/ダークモード対応
    --hover-l: #{lr($base-lr, if(dark-mode(), $base-lr + var(--hover-state-change), $base-lr - var(--hover-state-change)))};

    background-color: oklch(
      var(--hover-l, l(from var(#{$base-color}))), // 明度
      c(from var(#{$base-color})), // 彩度
      h(from var(#{$base-color}))  // 色相
    );
  }
}

.button {
  @include hover-state(var(--color), 50);
}

アクセシビリティなどは意識しないですむように

方法が思いつかない…がコントラスト比などの自動テスト?
TypeScriptベースの時はできたもののどうしよう

動的カラー対応

現状でもCSS変数で外部から注入してプリミティブカラーを変えれば一括で変わる形にはなっている

@romot-co
Copy link
Contributor Author

romot-co commented Dec 1, 2024

移行について

各コンポーネントカラーをコンポーネント側で定義して使う
便利関数などは必須ではなく特に問題は起こらないはず…?

@Hiroshiba
Copy link
Member

各コンポーネントカラーをコンポーネント側で定義して使う

そのコンポーネントの中で1回しか出ない色とかは、マストで定義はしなくても良いかも?
逆に何度も使うものは確かに1回定義して使い回した方が絶対良さそう!

@romot-co
Copy link
Contributor Author

romot-co commented Feb 12, 2025

ChatGPTくんに聞いた結果のメモ

ChatGPTくんに聞いた結果のメモです(いちおうコンセプトだけはわずかに考えた)
メンテしやすいのかなど細かく見られていないです


以下の設計は、各ロールの「デフォルト値」(すなわち base 明度値)だけを指定すれば、その他の状態(on、container、hover、disabled など)の値はあらかじめ決められたオフセットを自動的に加算・減算することで算出される、というコンセプトに基づいています。

つまり、利用側は light テーマ・dark テーマそれぞれの各ロールについて基本の明度(base)のみを定義すればよく、その他の状態のカラー値は内部ロジックで自動計算され、例えば

  • --scheme-color-primary(デフォルト)
  • --scheme-color-primary-hover
  • --scheme-color-primary-on
  • --scheme-color-primary-container
    などが自動で生成されます。

以下に、コンセプトを明示した上で、修正後の完全な SCSS コードとドキュメントを示します。


カラーシステム設計ドキュメント

概要

本システムは OKLCH 表記を用いて、各カラーロール(primary、secondary、tertiary、error など)の色相・彩度・明度を管理します。
コンセプト:

  • デフォルト値のみ指定:
    各ロールの基本となる明度(base 値)だけをテーマごとに定義すれば、その他の状態(on、container、hover、disabled など)のカラー値は、あらかじめ設定されたオフセット値を加えることで自動的に算出されます。
  • 自動生成:
    CSS 変数は mixin を通して自動生成されるため、利用側は生成済みの変数(例:var(--scheme-color-primary-hover))をそのまま使用できます。
  • テーマ切替:
    HTML 側で is-dark-theme 属性("true" / "false")を設定するだけで、light テーマと dark テーマの配色が切り替わります。

システム構成

  1. 明度補正と LUT 生成:
    OKLCH の L 値を知覚的な明るさに合わせるため、Sass 関数 LtoLr() によって補正を行い、0~100 の各値に対して CSS 変数 --lr-0--lr-100 を定義します。

  2. 基本彩度・色相の定義:
    各ロールの彩度と色相を :root 内で定義します。

  3. テーマ設定:

    • デフォルト値(base)のみ指定:
      Light テーマおよび Dark テーマについて、各ロールの基本明度(base 値)をマップで指定します。
    • 状態オフセット:
      各状態(default、on、container、hover、disabled など)のオフセットは、あらかじめ定義されたマップ($state-deltas-light / $state-deltas-dark)で管理され、これを base 値に加えることで自動計算されます。
  4. テーマ生成 mixin:
    generate-theme mixin を用いて、各ロールの各状態のカラー値を自動計算し、CSS 変数として出力します。

  5. エイリアス生成:
    sing 系などの補助カラーも、neutral 系の彩度・色相を基に同じロジックで自動生成します。


修正後の完全な SCSS コード

@use "sass:math";

/*----------------------------------------------------
 1. 明度補正関数と LUT 生成
----------------------------------------------------*/
// OKLCH の L 値(0~100)を補正して知覚的な明るさに近づけるための関数
@function LtoLr($L) {
  $k1: 0.206;
  $k2: 0.03;
  $k3: calc((1 + $k2) / (1 + $k1));
  $l: calc($L / 100);
  $Lr: calc(
    (
      $k3 * $l - $k2 +
      math.sqrt(math.pow($k3 * $l - $k2, 2) + 4 * $k1 * $k3 * $l)
    ) / 2
  );
  @return $Lr;
}

// 0~100 の L 値に対応する補正済み明度(Lr値)を CSS 変数として定義
:root {
  @for $i from 0 through 100 {
    $Lr: LtoLr($i);
    --lr-#{$i}: #{calc($Lr)}; // 例: --lr-50 が補正後の 50% 明度
  }
}

/*----------------------------------------------------
 2. 基本となる彩度・色相の定義
----------------------------------------------------*/
:root {
  // 各ロールの彩度
  --primary-c: 0.098;
  --secondary-c: 0.036;
  --tertiary-c: var(--primary-c);
  --error-c: 0.196;
  --neutral-c: 0.003;
  --neutral-variant-c: 0.006;
  --link-c: 0.196;
  
  // 各ロールの色相
  --primary-h: 149.64;
  --secondary-h: var(--primary-h);
  --tertiary-h: calc(var(--primary-h) - 60);
  --error-h: 19.64;
  --neutral-h: calc(var(--primary-h) + 120);
  --neutral-variant-h: var(--primary-h);
  --link-h: 256;
}

/*----------------------------------------------------
 3. テーマ設定:デフォルト値(base)のみ指定し、各状態は自動計算
----------------------------------------------------*/
// Light テーマ用:各ロールの基本明度(base 値)のみ指定
$theme-roles-light: (
  primary: 40,
  secondary: 40,
  tertiary: 40,
  error: 40
);

// Light テーマ用:各状態のオフセット(base に対して加算・減算される値)
// ここで定義された値が自動的に各状態に適用されるため、利用側はデフォルト値のみを指定すればよい
$state-deltas-light: (
  default: 0,
  on: 60,
  container: 50,
  "on-container": -30,
  hover: 5,
  disabled: -20,
  fixed: 50,
  "fixed-dim": 40,
  "on-fixed": -30,
  "on-fixed-variant": -10
);

// Dark テーマ用:各ロールの基本明度(base 値)のみ指定
$theme-roles-dark: (
  primary: 80,
  secondary: 80,
  tertiary: 80,
  error: 80
);

// Dark テーマ用:各状態のオフセット
$state-deltas-dark: (
  default: 0,
  on: -60,
  container: -50,
  "on-container": 30,
  hover: -5,
  disabled: 20,
  fixed: -50,
  "fixed-dim": -40,
  "on-fixed": 30,
  "on-fixed-variant": 10
);

/*----------------------------------------------------
 4. テーマ生成用 mixin:自動計算で各状態の明度値を決定し CSS 変数を出力
----------------------------------------------------*/
@mixin generate-theme($theme-roles, $state-deltas) {
  @each $role, $base in $theme-roles {
    @each $state, $delta in $state-deltas {
      $computed: $base + $delta;
      // 値が 0~100 の範囲に収まるようにクランプ
      $computed: if($computed < 0, 0, if($computed > 100, 100, $computed));
      
      // default 状態は変数名を --scheme-color-<role>、その他は --scheme-color-<role>-<state> とする
      $var-name: if($state == default, $role, "#{$role}-#{$state}");
      
      :root {
        --scheme-color-#{$var-name}: oklch(var(--lr-#{$computed}) var(--#{$role}-c) var(--#{$role}-h));
      }
    }
  }
}

/*----------------------------------------------------
 5. テーマ生成:light / dark 両テーマの CSS 変数を生成
----------------------------------------------------*/
:root[is-dark-theme="false"] {
  @include generate-theme($theme-roles-light, $state-deltas-light);
}

:root[is-dark-theme="true"] {
  @include generate-theme($theme-roles-dark, $state-deltas-dark);
}

/*----------------------------------------------------
 6. 状態変化用補助 mixin(必要に応じて利用側で上書き可能)
----------------------------------------------------*/
@mixin state-adjust($role, $state, $delta) {
  --scheme-color-#{$role}-#{$state}: oklch(
    calc(var(--lr-#{$role}-base, var(--lr-40)) + #{$delta})
    var(--#{$role}-c) var(--#{$role}-h)
  );
}

/*----------------------------------------------------
 7. エイリアス(alias)生成:sing 系カラーの自動計算
----------------------------------------------------*/
// sing 系カラーも neutral 系の彩度・色相を基にして自動生成
// 利用側はデフォルト値(base 値)のみ指定し、その他は自動計算される仕組みです。
@mixin generate-alias-theme($alias-name, $base) {
  :root[is-dark-theme="false"] {
    @each $state, $delta in $state-deltas-light {
      $computed: $base + $delta;
      $computed: if($computed < 0, 0, if($computed > 100, 100, $computed));
      $var-name: if($state == default, $alias-name, "#{$alias-name}-#{$state}");
      --scheme-color-#{$var-name}: oklch(var(--lr-#{$computed}) var(--neutral-c) var(--neutral-h));
    }
  }
  :root[is-dark-theme="true"] {
    @each $state, $delta in $state-deltas-dark {
      $computed: $base + $delta;
      $computed: if($computed < 0, 0, if($computed > 100, 100, $computed));
      $var-name: if($state == default, $alias-name, "#{$alias-name}-#{$state}");
      --scheme-color-#{$var-name}: oklch(var(--lr-#{$computed}) var(--neutral-c) var(--neutral-h));
    }
  }
}

/* エイリアス生成例:sing 系カラー */
// sing-ruler 系
@include generate-alias-theme("sing-ruler-surface", 30);
@include generate-alias-theme("sing-ruler-measure-line", 60);
@include generate-alias-theme("sing-ruler-beat-line", 50);
// sing-piano-key 系
@include generate-alias-theme("sing-piano-key-white", 70);
@include generate-alias-theme("sing-piano-key-black", 20);

まとめ

この修正後のコードでは、利用者は各テーマにおけるロールのデフォルト(base)値だけを指定すればよく、その他の各状態の値は内部で自動的に計算・生成されるようになっています。
そのため、シンプルかつ堅牢でメンテナンスしやすいカラーシステムとして、実装・運用が容易となります。

@Hiroshiba
Copy link
Member

Hiroshiba commented Feb 19, 2025

なるほど、結構まとまりそうな気がしますね!!!
バリエーションが限られてるから、色を決める方法が分からない人がちょっとしたデザインを作る時、狙った色がなかったら諦められそう。
逆に勝手がわかる人は自分でバリエーション(エイリアス)を定義できそう!

ただSCSSだから、あとでユーザーがカスタムテーマを作ることはできなさそう。
まあいろんなバリエーション用意してあげればだいたい何とかなりそうだけど!!

@romot-co
Copy link
Contributor Author

@Hiroshiba
確認ありがとうございます!現状よりは使いやすくなりそうかもです。
開発で利用する時どうするのが使いやすいのか・メンテしやすいのかがちょっと読めていないかもですが、

  • 普通に使うならMaterialDesign3使えればいちおうわかる
  • 面倒な計算の部分はSASSかJSにカプセル化しておく

あたりを目指すのがよさそう…?
メンテしやすさや使いやすさについて、ヒホさんやみなさんにうかがえれば!

カスタムテーマ作りたい場合どうすればいいのか悩ましいですが...
SCSSですがCSS Variablesで出力されるので
変えたい変数を変えれば一応どうにかなるかも?なので検証してみます!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants