戴兜

戴兜的小屋

Coding the world.
github
bilibili
twitter

SCSS+WindiCSSによるテーマカラーの切り替えの実現

最近、自分のホームページ(兼ブログ)を作っているところですが、テーマカラーの切り替え機能を実装しました。ページにアクセスするたびに、ランダムにカラーテーマを選択して、ページをより活気づけるようにしました。以下のようになります:

image

これはどのように実現されているのでしょうか?まずはカスタムカラーから始めてみましょう。

WindiCSS のカスタムカラー#

固定のカラーを定義する#

// windi.config.js

export default defineConfig({
  theme: {
    extend: {
      colors: {
        primary: "#2196f3",
      },
    },
  },
})

これにより、primaryというカラーが定義され、以降は通常通り使用できます(例:bg-primary / text-primary)。

もちろん、文字列だけでなく、オブジェクトを使用してカラーグループを定義することもできます(例:bg-primary-light)。

// windi.config.js

export default defineConfig({
  theme: {
    extend: {
      colors: {
        primary: {
          extralight: "#d3eafd",
          light: "#b2dafb",
          medium: "#6ebbf7",
          DEFAULT: "#2196f3"
        },
      },
    },
  },
})

CSS 変数の使用#

カラーを可変にするために、CSS 変数を使用すると便利です。WindiCSS でももちろんサポートされています。

:root {
  --color-primary-extralight: #d3eafd;
  --color-primary-light: #b2dafb;
  --color-primary-medium: #6ebbf7;
  --color-primary: #2196f3;
  --color-primary-dark: #27415b;
}
// windi.config.js

export default defineConfig({
  theme: {
    extend: {
      colors: {
        primary: {
          extralight: 'var(--color-primary-extralight)',
          light: 'var(--color-primary-light)',
          medium: 'var(--color-primary-medium)',
          DEFAULT: 'var(--color-primary)',
          dark: 'var(--color-primary-dark)',
        },
      },
    },
  },
})

これにより、WindiCSS で CSS 変数を基本的に使用できるようになります。ただし、少し問題があります。

WindiCSS は、カラーに透明度を設定することができます。例えば、bg-gray-800/80bg-gray-800 bg-opacity-80のような書き方ができます。上記の設定方法では、このような構文が無効になってしまいます(透明度が失われます)。そのため、CSS 変数を別の形式に変更する必要があります。同時に、変数をラップするための高度なユーティリティ関数も必要です。

:root {
  --color-primary-extralight: 211 234 253;
  --color-primary-light: 178 218 251;
  --color-primary-medium: 110 187 247;
  --color-primary: 33 150 243;
  --color-primary-dark: 39 65 91;
}
// windi.config.js

function withOpacityValue(variable) {
  return val => {
    if (val.opacityValue === undefined) {
      return `rgb(var(${variable}))`
    }
    return `rgb(var(${variable}) / ${val.opacityValue})`
  }
}

export default defineConfig({
  theme: {
    extend: {
      colors: {
        primary: {
          extralight: withOpacityValue('--color-primary-extralight'),
          light: withOpacityValue('--color-primary-light'),
          medium: withOpacityValue('--color-primary-medium'),
          DEFAULT: withOpacityValue('--color-primary'),
          dark: withOpacityValue('--color-primary-dark'),
        },
      },
    },
  },
})

これにより、primaryカラーを使用するたびに、WindiCSS は関数を呼び出してスタイルを生成し、opacityValueの判定によって透明度の構文をサポートします。

SCSS で CSS 変数を生成する#

明らかに、lightextralightなどのカラーバリエーションに手動で色を指定することは現実的ではありません。また、RGB の 3 つの数字で色を表現する必要があり、エディタではハイライトが表示されず、直感的ではなく、メンテナンスが困難になります。

ここで、SCSS が役立ちます!SCSS は基本的な CSS データ型、条件分岐、ループ構文を提供しており、さまざまなユーティリティ関数(例:red() blue() green()などのチャネル分離、mix()などのカラーブレンド)も提供しています。

まず、渡された 16 進数のカラーを RGB の 3 つの数字に変換するユーティリティ関数を実装してみましょう。

@function getColorValue($color) {
  @return #{red($color)} #{green($color)} #{blue($color)};
}

/* getColorValue(#2196f3) -> 33 150 243 */

私の予想では、primaryの基本色を指定するだけで、SCSS がlightextralightなどのカラーバリエーションをすべて生成してくれるはずです。mixメソッドを使用して実現します。

@mixin spread-theme-map($map: ()) {
  @each $key, $value in $map {
    #{"--"+$key}: $value;
  }
}

@function theme-primary-map($primary-color: #2196f3) {
  @return (
    color-primary-dark: getColorValue(mix($primary-color, black, 30%)),
    color-primary: getColorValue($primary-color),
    color-primary-medium: getColorValue(mix($primary-color, white, 70%)),
    color-primary-light: getColorValue(mix($primary-color, white, 35%)),
    color-primary-extralight: getColorValue(mix($primary-color, white, 15%))
  );
}

/* spread-theme-map(theme-primary-map(#2196f3)) */

これにより、特定のカラーに対応するカラープロパティを生成できます。次に、必要なテーマカラーを配列に定義し、ループを回すだけです(Material Design のドキュメントからいくつかの鮮やかな色を選びました)。

$themeColorList: (
  #2196f3,
  #f44336,
  #9c27b0,
  #4caf50,
  #3f51b5,
  #795548,
  #607d8b,
  #009688
);

@for $i from 1 through length($themeColorList) {
  $color: nth($themeColorList, $i);
  .theme-#{$i} {
    @include spread-theme-map(theme-primary-map($color));
  }
}

VSCode では、次のように表示されます:

image

明らかに使いやすくなりました。

残りの作業取り消し線を引く#

テーマカラーを変更したい場合は、ルート要素(htmlまたはbody)に対応するクラスを追加するだけで済みます(例:theme-1 / theme-2)。実現方法はさまざまですが、私は Nuxt.js を使用しているため、次のような解決策を採用しました。

const randomThemeColorIndex = useState('randomThemeColorIndex', () =>
  Math.floor(Math.random() * themeColorList.length) + 1
)

useHead({
  bodyAttrs: {
    class: 'theme-' + randomThemeColorIndex.value,
  }
})
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。