blog

Sass/SCSSでSMACSS(モジュール・状態・テーマ)を意識した色の管理方法について模索してみた

    • Ryuichi Nonaka
    この記事は書かれてから1年以上経過しており、内容が古い場合があります。

    はじめに

    2014年の暮れぐらいからSass / SCSSでどうやって色(カラーコード)を管理するか色々と模索してきて、また懲りずにSMACSS(モジュール・状態・テーマ)を意識した色の管理方法を考えてみたので備忘録として記事にすることにした。設計についてはデザイナーが作ったものだと思って寛容に見て欲しい。もっと良い設計方法があるよというアドバイスがあれば是非コメントをお願いしたい。

    まとまったソースコードを見たい人はGistを参照。

    過去に試した管理方法

    まずはお復習いというか、過去に試したのは2つの管理方法について。

    1. SCSSでカラーコードを管理する関数を作る
    2. SCSSでカラーコードをパレット・トーン・ステートで管理できるMixinを作る

    前者は非常にシンプルでSassのmap-get()を使って配列から値を取得しているだけ。後者は少し複雑(配列が増えただけ)になり、グルーピングできるようになった。

    使いにくかった点

    この2つを使って来て使いにくかった点は関数の引数指定方法が冗長で同じ指定の記述が多く、SMACSSを意識したCSS設計にマッチしていなかった(原因は設計力がなさ過ぎである)。

    • 引数の指定に無駄がある(省略できない)
    • あるテーマの時に状態(:hoverや:active等)の色をパレット1に持てない。
    • パレット上でプライマリカラー等の使い回しができない
    • パレット上で色の差(hover時より明るい・暗い等)を把握できない(HSB,HSL使えってのは一理ある)
    • 共通利用するグローバルパレットとモジュール単位で管理するローカルパレットを分けたい
    • 明るさ(darken()lighten())・彩度に規則性を持たせたい(適当に指定できると後々管理しにくくなる)

    今回作ってみた管理方法

    上記の課題を解決するためにいくつかのFunctionを作ってみた。

    色の管理

    サイト全体やモジュール間で共通利用されるレベルのカラーコードをグローバル変数に入れ、定数のように定義する。ここでは状態に関する色は一切持たせないが、テーマを作る場合に$COLOR__TEXT-BASE$COLOR__TEXT-INVERSEのように分ける。細かなテーマや状態に関する色は後述するパレットで管理する。

    $COLOR__PRIMARY:        #fafafa;
    
    $COLOR__TEXT-BASE:      #444444;
    $COLOR__TEXT-INVERSE:   #eeeeee;
    
    $COLOR__BG-BASE:        #fafafa;
    $COLOR__BG-INVERSE:     #242B33;
    
    $COLOR__THEME-RED:      #E84E3C;
    $COLOR__THEME-ORANGE:   #F29933;
    $COLOR__THEME-YELLOW:   #F2C10F;
    $COLOR__THEME-LIME:     #99DE37;
    $COLOR__THEME-GREEN:    #33D463;
    $COLOR__THEME-EMERALD:  #31DEA7;
    $COLOR__THEME-CYAN:     #36CFE0;
    $COLOR__THEME-AQUA:     #49AAE3;
    $COLOR__THEME-BLUE:     #427AE3;
    $COLOR__THEME-DEEPBLUE: #4353E0;
    $COLOR__THEME-VIOLET:   #8B41E0;
    $COLOR__THEME-PURPLE:   #C34FE0;
    $COLOR__THEME-WINERED:  #EB497F;
    
    $COLOR__BRAND-FACEBOOK: #3B5998;
    $COLOR__BRAND-TWITTER:  #00acee;
    $COLOR__BRAND-HATENA:   #2c6ebd;
    $COLOR__BRAND-POCKET:   #ee4056;
    $COLOR__BRAND-GPLUS:    #dd4c39;
    $COLOR__BRAND-FEEDLY:   #2bb24c;
    

    この色をベースにモジュール単位で分けるようにパレット(連想配列)を作る。キー(card)をがモジュール名とし、その中の配列にテーマ(base)と状態(normal, hover)毎の背景色や文字色などのCSSプロパティに合わせたカラーコードを持たせる。

    $PALETTES: (
      'card': ( 
        'base': (
          'normal': (
            border:     transparent,
            color:      $COLOR__TEXT-BASE,
            background: $COLOR__BG-BASE,
            box-shadow: rgba( 0, 0, 0, .05 )
          ),
          'hover': (
            border:     $COLOR__THEME-DEEPBLUE,
            color:      brightness( $COLOR__TEXT-BASE, '-05x' ),
            background: brightness( $COLOR__BG-BASE, '05x' ),
            box-shadow: rgba( 0, 0, 0, .1 )
          ),
          'current': (
            border:     #333,
            color:      #fff,
            background: #444,
            box-shadow: rgba( 0, 0, 0, .05 )
          )
        )
      ),
      'button': ( 
        'base': (
          'normal': (
            icon__border:  #ccc,
            lavel__border: #ddd
          ),
        )
      )
    );
    

    カラーコードは直接指定しても良いし、グローバル変数に定義した$COLOR__THEME-DEEPBLUE等のなんちゃって定数を使えば再利用性が高くなる。加えて1つのカラーコードから状態に合わせて明るさや彩度を調整するための関数brightness()saturation()を使って、brightness( $COLOR__THEME-DEEPBLUE, '2x' )のように状態に合わせて明るさ、彩度を上げ下げすれば個別にカラーコードを指定しなくて済む。brightness()saturation()は第一引数に対象のカラーコード、第二引数にamountに置き換えられる値を入れる。

    brightness()darken()lighten()のWrapper関数で値に合わせて一定間隔で指定されたamountを使う。saturation()saturate()desaturate()のWrapper関数で同じロジックだ。darken()lighten()を直接使うとamountがその時の気分や人で変わりやすいので一定の制限を設けるために使う。関数の詳細は記事下部に貼ってあるGistを参照。

    $tones: (
      '8x'        : ('darken',  64%),
      '7x'        : ('darken',  56%),
      '6x'        : ('darken',  48%),
      '5x'        : ('darken',  40%),
      '4x'        : ('darken',  32%),
      '3x'        : ('darken',  24%),
      '2x'        : ('darken',  16%),
      '1x'        : ('darken',   8%),
      '05x'       : ('darken',   4%),
      'normal'    : ('none',     0%),
      '-05x'      : ('lighten',  4%),
      '-1x'       : ('lighten',  8%),
      '-2x'       : ('lighten', 16%),
      '-3x'       : ('lighten', 24%),
      '-4x'       : ('lighten', 32%),
      '-5x'       : ('lighten', 40%),
      '-6x'       : ('lighten', 48%),
      '-7x'       : ('lighten', 56%),
      '-8x'       : ('lighten', 64%)
    );
    

    カラーコードの取得方法

    簡単なモジュールに今回の管理方法を採用してみた例がこれ。

    @import 'libs/color_palettes';
    @import 'config/colors';
    @import 'config/palettes';
    
    .component-card {
      // モジュールの定義する
      $COMPONENT: (
        //palettes: $PALETTES <- パレットの指定(デフォルトでグローバルのパレットを呼ぶ)
        name: 'card', // <- モジュール名を指定する(必須)
        //state: 'normal', <- 状態(デフォルトがnormal
        //theme: 'base' <- テーマ(デフォルトがbase
      );
    
      margin    : 2rem auto;
      padding   : 3rem;
      min-width :  30%;
      max-width :  60%;
    
      border-radius : 2px;
      border        : 1px solid palette( 'border', $COMPONENT ); // <- パレットからborderの色を返す
    
      text-align : center;
      color      : palette( 'color', $COMPONENT );
    
      background : palette( 'background', $COMPONENT );
      box-shadow : 0 .05rem .2rem palette( 'box-shadow', $COMPONENT );
    
    
      &:hover {
        // オプションを再指定してhover時のパレットを取得するよう定義する
        $HOVER: set-palette-option( $COMPONENT, 'state', 'hover' );
    
        border     : 1px solid palette( 'border', $HOVER ); // <- hover時のborderカラーが返ってくる
        color      : palette( 'color', $HOVER );
        background : palette( 'background', $HOVER );
        box-shadow : 0 .05rem .75rem palette( 'box-shadow', $HOVER );
      }
    
    
      &.state-current {
        $CURRENT: set-palette-option( $COMPONENT, 'state', 'current');
    
        border     : 1px solid palette( 'border', $CURRENT );
        color      : palette( 'color', $CURRENT );
        background : palette( 'background', $CURRENT );
        box-shadow : 0 .05rem .5rem palette( 'box-shadow', $CURRENT );
      }
    }
    

    これで、カラーコードを直接埋め込むことが無くなり別のモジュールに再利用し易くなるし、色を個別に指定することも減るので統一感が取れ、プロダクトの品質が向上する。グローバルで色を管理したくない場合は以下のようにモジュール内、もしくは別のグローバル変数でパレットを定義しても良い。

    .component-card {
      $COMPONENT: (
        palette: (
          'card': ( 
            'base': (
              'normal': (
                border:     transparent,
                color:      $COLOR__TEXT-BASE,
                background: $COLOR__BG-BASE,
                box-shadow: rgba( 0, 0, 0, .05 )
              ),
              'hover': (
                border:     $COLOR__THEME-DEEPBLUE,
                color:      brightness( $COLOR__TEXT-BASE, '-05x' ),
                background: brightness( $COLOR__BG-BASE, '05x' ),
                box-shadow: rgba( 0, 0, 0, .1 )
              ),
              'current': (
                border:     #333,
                color:      #fff,
                background: #444,
                box-shadow: rgba( 0, 0, 0, .05 )
              )
            )
          )
        ),
        name: 'card'
      );
    
      ...
    }
    

    どこまで使えるか

    まだ実プロジェクトでは使えていないので調整しながらどこまで使い物になるか引き続き試して、改良していこうと思う。目指しているのは自分が管理しているプロダクトで再利用可能なコンポーネント群(Refillsがイメージに近い)を作りたい。

    今回のソースコード

    クソコードでリファクタリングもろくにできていないけれど、とりあえず現状のものをGistに上げておくので何かの参考になればと思う。

    俺俺コンポーネントって楽しいよね。 ではでは。


    1. パレットというのはカラーコードをまとめているSassの配列のこと 

    コメント・フィードバック