CSSフレームワーク「Bulma」のmixinを眺める その1


このエントリでは、CSSフレームワーク「Bulma」のmixin.sassを見ていきます。

スニペットとしても、計算関数としても便利な@mixinは、CSSフレームワーク上ではどのように使われているのでしょう。 一つ一つコードを見て、内容を把握していきます。

※「Bulma」の元ファイルは.sassファイルですが、多く用いられている.scssファイル形式の記述に書き換えています。

arrow

@mixin arrow($color) {
  border: 1px solid $color;
  border-right: 0;
  border-top: 0;
  content: " ";
  display: block;
  height: 0.5em;
  pointer-events: none;
  position: absolute;
  transform: rotate(-45deg);
  width: 0.5em;
}

渡される引数

機能

渡された色で矢印を作る ためのCSSを出力します。

この@mixinは、select要素がプルダウン可能であることを表す下向き矢印に使われます。 左辺と底辺にborderをつけて直角にしたものを、45度回転させて下向き矢印に見せています。

block

@mixin block {
  &:not(:last-child) {
    margin-bottom: 1.5rem
  }
}

機能

最後の要素以外にmargin-bottom: 1.5remを持たせるCSSを返します。
セクションを区切るために使われています。

余白を@mixinで運用するメリット

余白指定を@include blockで運用することで、要素間のマージンの統一性が上がります。 どんなコンポーネントを並べても、統一してmargin-bottom: 1.5remがつくため、レイアウトに一貫性が生まれます。

また例えば、もしmargin-bottom: 2remにしたくなった場合などにも有効です。 @include blockでの運用をしていた場合は @mixinの内容のみ を修正すれば、@includeで呼び出している部分がすべて書き換わります。

一方で、直接margin-bottom: 1.5remのように数値指定していた場合、全ての指定箇所を書き換えていく必要があります。

margin-bottom: 1.5remは別の場所で使われている可能性もあるので、一括置換は難しいです。

clearfix

@mixin clearfix {
  &:after {
    clear: both;
    content: " ";
    display: table;
  }
}

floatレイアウトに用いるclearfixです。

ヘルパークラスとどっちが便利?

それぞれにメリットがあります。

@mixinを用いた場合、htmlでクラスを指定する必要がなくなる というメリットがあります。

ヘルパークラスを用いた場合は、CSSファイルの中身が煩雑にならないという点、HTML側で明示的にclearfixがかかっていることがわかるという点がメリットです。

フレームワークは運用コストを低くするためのものなので、「使うときに必要な作業を減らす」という点で@mixinによる運用が合理的な選択です。

center

@mixin center($size) {
  left: 50%;
  margin-left: -($size / 2);
  margin-top: -($size / 2);
  position: absolute;
  top: 50%;
}

渡される引数

機能

オブジェクトの中央揃えに用いる@mixinです。position: absoluteで配置する必要のある要素に用います。

いちいち position: absoluteで天地左右50%の位置に配置して、オブジェクトのwidthプロパティの値の半分をネガティブマージンでずらす」 という指定を、widthプロパティの数値を渡すだけで返してくれるので非常に便利です。

delete

mixin delete {
  // We need even pixel dimensions to ensure the delete cross can be perfectly centered
  $dimension-small: roundToEvenNumber(1.5 * removeUnit($size-6) * removeUnit($size-small)) * 1px;
  $dimension-normal: roundToEvenNumber(1.5 * removeUnit($size-6) * removeUnit($size-normal)) * 1px;
  $dimension-medium: roundToEvenNumber(1.5 * removeUnit($size-6) * removeUnit($size-medium)) * 1px;
  $dimension-large: roundToEvenNumber(1.5 * removeUnit($size-6) * removeUnit($size-large)) * 1px;
  @include unselectable;
  -moz-appearance: none;
  -webkit-appearance: none;
  background-color: rgba($black, 0.2);
  border: none;
  border-radius: 290486px;
  cursor: pointer;
  display: inline-block;
  font-size: $size-normal;
  height: $dimension-normal;
  outline: none;
  position: relative;
  transform: rotate(45deg);
  transform-origin: center center;
  vertical-align: top;
  width: $dimension-normal;
  &:before,
  &:after {
    background-color: $white;
    content: "";
    display: block;
    left: 50%;
    position: absolute;
    top: 50%;
    transform: translateX(-50%) translateY(-50%);
  }

  &:before {
    height: 2px;
    width: 50%;
  }

  &:after {
    height: 50%;
    width: 2px;
  }

  &:hover,
  &:focus {
    background-color: rgba($black, 0.3);
  }

  &:active {
    background-color: rgba($black, 0.4);
  }

  // Sizes
  &.is-small {
    height: $dimension-small;
    width: $dimension-small;
  }

  &.is-medium {
    height: $dimension-medium;
    width: $dimension-medium;
  }

  &.is-large {
    height: $dimension-large;
    width: $dimension-large;
  }

}

閉じるボタンです。

「x」を構成する斜め線は、::before擬似クラスと::after擬似クラスを用いています。 widthプロパティを親要素のwidthプロパティの50%height:1pxの長方形を45度傾けたものを中心で重ねています。

数値を偶数に丸め込むroundToEvenNumber関数

ここでは、完璧に中央に配置するために、数値を偶数に丸め込むroundToEvenNumber関数が用いられています。

理由は、土台となる要素のwidthプロパティが奇数だった場合、::before及び::after擬似クラスはwidth: 50%;で幅を指定しているので、実際のpx値が小数になってしまいます。

pxの小数点以下の処理は強制的に切り捨てであったり、四捨五入であったりとブラウザによりけりなので、完璧に中央配置するには親要素のwidthプロパティの値が偶数である必要があります。

fa

@mixin fa($size, $dimensions) {
  display: inline-block;
  font-size: $size;
  height: $dimensions;
  line-height: $dimensions;
  text-align: center;
  vertical-align: top;
  width: $dimensions;
}

font awesomeアイコンの配置用@mixin

渡された数値(=アイコンフォントに指定したいfont-sizeプロパティの値)を1辺とした空間を作り、text-align: center及びvertical-align: topを用いて空間内にアイコンを配置します。

これにより、アイコンの縦軸を揃えることができます。

hamburger

@mixin hamburger($dimensions) {
  cursor: pointer;
  display: block;
  height: $dimensions;
  position: relative;
  width: $dimensions;
  span {
    background-color: $text;
    display: block;
    height: 1px;
    left: 50%;
    margin-left: -7px;
    position: absolute;
    top: 50%;
    transition: none $speed $easing;
    transition-property: background, left, opacity, transform;
    width: 15px;
    &:nth-child(1) {
      margin-top: -6px;
    }

    &:nth-child(2) {
      margin-top: -1px;
    }

    &:nth-child(3) {
      margin-top: 4px;
    }
  }

  &:hover {
    background-color: $background;
  }

  // Modifers
  &.is-active {
    span {
      background-color: $link;
      &:nth-child(1) {
        margin-left: -5px;
        transform: rotate(45deg);
        transform-origin: left top;
      }
      &:nth-child(2) {
        opacity: 0;
      }

      &:nth-child(3) {
        margin-left: -5px;
        transform: rotate(-45deg);
        transform-origin: left bottom;
      }
    }
  }
}

渡される引数

機能

ハンバーガーメニューです。

渡された値を一片とした正方形をつくり、その中に三本線をposition:absoluteで配置しています。

.is-activeクラスを付与することで真ん中の線がopacity:0になり透明に、上下の線が45度ずつ傾き、中心で交わり「×」になります。

spinAround

@keyframes spinAround {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(359deg);
  }
}

回転アニメーションで用いるキーフレームです。

この次のloaderで使います。

loader

@mixin loader {
  animation: spinAround 500ms infinite linear;
  border: 2px solid $border;
  border-radius: 290486px;
  border-right-color: transparent;
  border-top-color: transparent;
  content: "";
  display: block;
  height: 1rem;
  position: relative;
  width: 1rem;
}

ローディングアニメーション表示用の@mixinです。

animation: spinAround 500ms infinite linear;で、0度から359度まで、等速で、無限にアニメーションし続ける ということを意味します。

他のプロパティは半円を描く役割をしています。

結果、半円がぐるぐる周り続けるローディングアニメーション になります。

overflow-touch

@mixin overflow-touch {
  -webkit-overflow-scrolling: touch;
}

iOS Sarariで慣性スクロールを有効にします。

Modalはheightプロパティを数値で指定しているので、内包する文章が長いと枠外にはみ出てしまいます。

overflow: auto;を指定することで、デスクトップ版Chromeなどではスクロールが有効になるのですが iOS Safariでは 「二本指でスワイプ」 することでスクロールができるようになります。 バグでもなんでもないのですが、 これあんまり使わないのでわからないんですよね。

それを解決するのがoverflow-scrollingプロパティです。 これを指定することで一本指でスクロールができるようになります。

overlay

@mixin overlay($offset: 0) {
  bottom: $offset;
  left: $offset;
  position: absolute;
  right: $offset;
  top: $offset;
}

中央配置用@mixinです。

centerと違う点として、こちらは 親要素をカバーするように配置されます。

width:100%height:100%と組み合わせて使われています。

親要素のアスペクト比に強制的にフィットさせるような使い方ができます。

placeholder

@mixin placeholder {
  $placeholders: ':-moz' ':-webkit-input' '-moz' '-ms-input';
  @each $placeholder in $placeholders {
    &:#{$placeholder}-placeholder
      @content      
  }
}

input要素のplaceholderの装飾に用いられます。 実は文字色とか変えられるんですよね。

@eachを用いて$placeholdersのすべての要素についてループし、ベンダープレフィックス付きのクラスを生成しています。 (ここ、Autoprefixer効かないんでしょうか)

unselectable

@mixin unselectable {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

例えばpaginationの「…」などは装飾的に用いられているテキストなので選択できないようにします。

button要素などもドラッグで選択できたりするとダサいのでこれを宣言しています。 ネイティブアプリ感を出さないようにするための処置ですね。

おわりに

私は今まで@mixinをあまり使うことがなかったのですが、「Bulma」の@mixinを見ていて2つの利点に気づきました。

実際に運用するファイルが簡潔になる。

便利計算だけでなく、ただ単純にスニペットを呼び出すためだけに定義されている@mixinも多くあることがわかりました。

特にcenterのような中央配置スニペットなど、 内容がある程度予測でき、かつ記述することで逆に煩雑になるような場合は、適度にブラックボックス化 することで運用するファイルのコードをスッキリさせることができます。

編集箇所が一点に絞れる

これは前々回のCSSフレームワーク「Bulma」に学ぶSassの変数定義でも同じようなことがありました。

例えば、あらゆるコンポーネント間の隙間はmargin-bottom: 1.5remという風に取り決めがあったとします。 この取り決めに従い、全てのコンポーネントのレイアウト時にはmargin-bottom: 1.5remを記述しました。 しかし、 「やっぱり1.8remにしたい」 と言った時、直接記述していた場合は置換作業が発生します。

margin-bottom: 1.5remmargin-bottom: 1.8remに一括置換すると、レイアウトに関与しない部分でたまたまmargin-bottom: 1.5remを使用していた場合に意図せず書き換えてしまうかもしれません。そうすると、いちいちコンテキストを確認しながらの置換作業になり非常に手間がかかります。

一方で、@mixinで定義しておいて呼び出す場合は@mixin側の値を変えるだけで、呼び出している部分全ての値が変更されます。 margin-bottomという普遍的なスタイリングに名前空間を持たせている ということになります。

以上、@mixinを見ていく回でした。

次回は@mediaに関わる@mixinを見ていきます。