はじめまして。こんにちは。
スマートフォン版Amebaプラットフォームでフロントエンドの開発を担当している遠藤と申します。
今回は、新しくなったAmebaのCSSに関するお話を簡単にさせていただこうと思います。



$1 pixel|サイバーエージェント公式クリエイターズブログ-画面イメージ

Stylus

最近多く聞くようになったCSSメタ言語は、SassやLESSが有名ですが、今回はStylusというものを採用しました。
新しいAmebaはnode.jsを採用しており、当初HTMLテンプレートの選定でexpressモジュールのデフォルトでも使われているJadeが候補となり、それと同時にCSSではStylusが挙がりました。
JadeとStylusは作者が同じexpressモジュールの開発者であるため、expressモジュールと相性がよく、記法が似ていてタブによる階層構造で記述するのが特徴です。
Stylusは初めて触りましたが、SassとLESSが持っている機能は基本的に兼ね備えており、コードの見通しの良さと、変更のしやすさから採用を決定しました。
Stylus

サンプルコード(Stylus)

// common prop
com-bdc = #ccc
com-bd = solid 1px com-bdc
fr = 4px

// mixin
vendor(prop, args)
  -webkit-{prop} args
  {prop} args

border-radius(var)
  vendor('border-radius',var)

// style
.extend_sample_class
  display block
  border com-bd

.sample_box
  @extend .extend_sample_class
  border-radius(fr)
  &.heading
    border-bottom com-bd
  .sample_container &
    background red

最初は見慣れないので書き辛いように感じますが、慣れてくると{}や:;を書かなくてもいいことと、セレクタの親子関係をタブ一つで変えることができるので、コーディングが非常に楽になります。


サンプルコード(CSS)

.extend_sample_class,.sample_box{
  display:block;
  border:solid 1px #ccc;
}
.sample_box{
  -webkit-border-radius:4px;
  border-radius:4px;
}
.sample_box.heading{
  border-bottom:solid 1px #ccc;
}
.sample_container .sample_box{
  background:#f00;
}


StylusとCSSファイル

Stylusのファイル構造を以下のようにすることで、変更があった際に対応しやすくしています。
基本的に追加・改修が頻繁に発生するため、可能な限り共通化を行い、ユニークなレイアウトのみ、各画面用のstylusに記述する形をとっています。

  • ノーマライズ
     各ブラウザ間で相違のあるstyleの共通化
  • 共通変数&mixin&extend
     共通で使う色やサイズの変数、mixin、extendの定義
  • コンポーネント
     共通で使うCSSコンポーネント定義
  • 共通モジュール
     共通で使うCSSモジュール定義
  • 共通全体import
     共通系stylusのimport
  • 各画面用スタイル
      各画面ごとの特殊なstyleの定義

StylusファイルとCSSファイル構成イメージ


node.js+expressモジュールを使う前提であれば、Stylusのコンパイルはnodeで行えますが、そういう環境ではない場合、Macの方にはCodeKitというツールがお勧めです。
CSS系ではStylusだけでなく、SassやLESSもコンパイルできますし、CofeeScriptやHTMLテンプレートのJadeやHamlなども対応しています。
liveReloadも行なってくれるので、かなり開発が楽になります。
CodeKit


サムネイル画像

ユーザやアプリなどのサムネイル画像の表示方法ですが、今回はimg要素ではなく、i要素に背景として設定するようにしています。
サムネイルの上にオンライン表示のマークや編集アイコンをかぶせたり、サムネイル自体にCSSで装飾を施す場合、img要素は空要素なのでCSSの擬似要素である::beforeや::afterを使って要素の追加をすることができません。
また、画像のサイズ変更の際に、img要素にしていると変倍されてしまったり表示崩れの原因になることもあります。
マークアップとしては本来img要素にすべきですが、デザイン変更を柔軟に行うためにi要素の背景としました。
i要素は代替音声や気分などを表す要素で、img要素の代替として使用するのに適していると判断しました。

デザイン

1 pixel|サイバーエージェント公式クリエイターズブログ-サムネイル

サンプルコード(Stylus)※一部抜粋


border-radius(size)
  vendor('border-radius',size)

box-sizing(prop)
  vendor('box-sizing',prop)

square(size)
  height unit(size,'px')
  width @height

.thumbnail
  display inline-block
  box-sizing(border-box)
  square(50)
  border solid 1px #ddd
  background-color #fff
  background-size cover
  background-repeat no-repeat
  background-position center center
  border-radius 2px
  &.online
  &.offline
    position relative
    &::after
      content ''
      position absolute
      bottom 1px
      left auto
      right -4px
      display block
      box-sizing(border-box)
      height 14px
      width @height
      margin 0 5px -1px 0
      border solid 1px #fff
      background-color #ccce
      border-radius(@height/2)

  &.online
    &::after
      background #6fc10d

  &.large
    square(100)
    border-radius 4px
  &.small
    square(40)

サンプルコード(CSS)


.thumbnail {
  display: inline-block;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  height: 50px;
  width: 50px;
  border: solid 1px #ddd;
  background-color: #fff;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center center;
  -webkit-border-radius: 2px;
  border-radius: 2px;
}
.thumbnail.online,
.thumbnail.offline {
  position: relative;
}
.thumbnail.online::after,
.thumbnail.offline::after {
  content: '';
  position: absolute;
  bottom: 1px;
  left: auto;
  right: -4px;
  display: block;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  height: 14px;
  width: 14px;
  margin: 0 5px -1px 0;
  border: solid 1px #fff;
  background-color: rgba(204,204,204,0.933);
  -webkit-border-radius: 7px;
  border-radius: 7px;
}
.thumbnail.online::after {
  background: #6fc10d;
}
.thumbnail.large {
  height: 100px;
  width: 100px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
}
.thumbnail.small {
  height: 40px;
  width: 40px;
}

アイコン

サイトを彩る上で欠かせないアイコンですが、スマートフォンの場合、解像度別に画像を作成して、配置を調整して…とやると、変更、追加が多いサービスでは運用しづらくなってしまいがちです。
また、画像が多くなるにつれて、スプライトで実装をしても、画像ファイルが重くなり、あとから表示されてしまいます。
これを多少でも改善するため、アイコンをフォントで作成しました。

デザイン


これにより、大きさや色の違うアイコンであっても、作り直す必要もなくなり、重くなり過ぎて後から表示されるようなことも改善されています。
コードは、ちょっと不恰好ですが、各文字のクラスに対応した文字が入るものを用意しました。
記号に当たるものに限り、名称をつけています。

サンプルコード(Stylus)※一部抜粋

symbols = "a","b","c","d","e","f","g","h","i","j"
marks = ('footstamp' '"')('add' '+')('back' '<')('allow' '>')('crown' '¥')('alert' '!');

.i
  display inline-block
  font-family 'AmebaSymbols'
  font-size 2.0rem
  line-height 1

.icon
  @extend .i
  color tc-default !important
  vertical-align -.2em

  for t in symbols
    &.{t}::before
      content t

  for key,index in marks
    &.{key}::before
      content key[1]

サンプルコード(CSS)

.i,.icon{
  display:inline-block;
  font-family:'AmebaSymbols';
  font-size:2rem;line-height:1;
}
.icon{vertical-align:-.2em;}
.icon.a::before{content:"a"}
.icon.b::before{content:"b"}
.icon.c::before{content:"c"}
.icon.d::before{content:"d"}
.icon.e::before{content:"e"}
.icon.f::before{content:"f"}
.icon.g::before{content:"g"}
.icon.h::before{content:"h"}
.icon.i::before{content:"i"}
.icon.j::before{content:"j"}
.icon.footstamp::before{content:'"'}
.icon.add::before{content:'+'}
.icon.back::before{content:'<'}
.icon.allow::before{content:'>'}
.icon.crown::before{content:'¥'}
.icon.alert::before{content:'!'}

スプライト画像の扱い

スマートフォンサイトでは、画像を最適に表示するために、1倍、1.5倍、2倍のサイズ違いの画像を用意剃る必要があります。
装飾で使う画像はCSSスプライトという技法を用いることが多いですが、変更や追加で画像サイズに変更がある場合、CSSのバックグラウンドサイズも変えなければなりません。
Stylusには、画像のサイズを取得するメソッドが用意されているため、これを採用して運用コストを減らしました。
また、最適な解像度の画像の選択ですが、メディアクエリを用いる方法では、対象端末以外では無駄なコードになってしまうため、javascriptを使って、pixel ratioを取得し、CSSを書き換えることで最適な画像を表示するようにしています。

サンプルコード(Stylus)

image-path = '../../../../public/img/1'
bg(name)
  path = '../img/$ratio/external/'+name
  overflow hidden
  background-image url(path)
  background-size image-size(image-path + '/external/'+ name) style="color:#f00">// 画像のサイズを取得
  background-repeat no-repeat

.bg-sprite
  bg('amb_bg_sprite.png')
  text-indent 100%

.ameba
  display block
  height 18px
  width 85px
  @extend .bg-sprite
  background-position 0 0

サンプルコード(CSS)

.bg-sprite,.ameba {
  overflow:hidden;
  background-image:url("../img/$ratio/amb_bg_sprite.png"); // $ratioをjavascriptで端末のpixelratioに書き換え
  background-size:285px 65px;
  background-repeat:no-repeat;
  text-indent:100%;
}

サンプルコード(Javascript)

function adjustCss(href) {
  var pixelRatio = window.devicePixelRatio || 1;
  // find target css
  var stylesheets = document.styleSheets;
  for (var i = 0; i < stylesheets.length; i++) {
    var stylesheet = stylesheets[i];
    if (stylesheet.ownerNode.getAttribute('href') == href) {
      var rules = stylesheet.cssRules;
      for (var j = 0; j < rules.length; j++) {
        var rule = rules[j];
        var style = rule.style;
        if (style) {
          var bgimage = style.getPropertyValue('background-image');
          if (bgimage && bgimage.indexOf('$ratio') >= 0) {
            rule.style.setProperty('background-image', bgimage.replace(/\$ratio/, pixelRatio), null);
          }
        }
      }
    }
  }
}

最後に

細かい点は色々と端折らせていただきましたが、このような感じでCSSを構築しております。
今回のプラットフォームの構築にあたり、構築過程で大きなデザイン変更が3度ほどありましたが、こういった設計によって、基本的にCSSの変更だけで対応できています。

Stylusに関しては、初めて使用しましたが、Sass+Compassほどの高機能でもなく、LESSよりやれることが多いといった印象で、個人的には非常に使いやすく感じました。
記法への慣れは必要ですが、:や;や{}を書かなくて済むのと、コードが非常に見やすくなるので、今後は積極的に使ってみようと思っています。