読者です 読者をやめる 読者になる 読者になる

【less】今更始めるlessの基本文法とtips(2) - mixin編

前書き

前回の続きです。

mixin

lessの特色はほとんどこのmixinに集約されていると言っても過言ではありません。

mixinの宣言

宣言と言うほどでもなく、既に定義したセレクタを呼び出せる機能です。

// less
.mixin {
  color: #000;
}

div {
  .mixin;
}

/* css */
.mixin {
  color: #000;
}
div {
  color: #000;
}

クラスではなくIDでも呼べますが、あんまりやらないっぽいですね。

// less
#mixin {
  color: #000;
}

div {
  #mixin;
}

/* css */
#mixin {
  color: #000;
}
div {
  color: #000;
}

名前空間

mixinはネストされた要素もすべて継承します。

// less
.mixin {
  span {
    color: #000;
  }
  .hoge {
    color: #fff;
  }
}

div {
  .mixin;
}

/* css */
div span {
  color: #000;
}
div .hoge {
  color: #fff;
}

ある特定の要素だけをmixinしたい場合はセレクタ > セレクタとすることで可能です。

// less
.mixin {
  span {
    color: #000;
  }
  .hoge {
    color: #fff;
  }
}

div {
  .mixin > .hoge;
}

/* css */
div {
  color: #fff;
}

この機能を使うことで、mixinに名前空間ライクなものを付与することができます。

// less
// ベンダプレフィックスを吸収するためのmixin
#vendor {
  .border-radius {
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    border-radius: 4px;
  }
}

div {
  #vendor > .border-radius;
}

/* css */
div {
  -moz-border-radius: 4px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
}

!importantの扱い

mixin時に!importantをつけるとmixinされる全プロパティに!importantが付与されます。

// less
#vendor {
  .border-radius {
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    border-radius: 4px;
  }
}

div {
  #vendor > .border-radius !important;
}

/* css */
div {
  -moz-border-radius: 4px !important;
  -webkit-border-radius: 4px !important;
  border-radius: 4px !important;
}

引数つきのmixin

mixinには引数をつけることが可能です。

// less
#vendor {
  .border-radius(@param) {
    -moz-border-radius: @param;
    -webkit-border-radius: @param;
    border-radius: @param;
  }
}

div {
  #vendor > .border-radius(4px);
}

/* css */
div {
  -moz-border-radius: 4px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
}

複数の引数を渡す

引数は,;で区切って宣言することができます。

lessとしては;を推奨しています。理由は、引数にデフォルト値を与えるときに,を使えるからです。(デフォルト値については後述)

// less
#vendor {
  .border-radius(@left-top; @right-top; @right-bottom; @left-bottom) {
    -moz-border-radius: @left-top @right-top @right-bottom @left-bottom;
    -webkit-border-radius: @left-top @right-top @right-bottom @left-bottom;
    border-radius: @left-top @right-top @right-bottom @left-bottom;
  }
}

div {
  // 引数を渡す時も";"が使える
  #vendor > .border-radius(4px; 2px; 3px; 5px);
}

しかしまぁ、上記の記述だとちょっと冗長すぎます。@argumentsと言う暗黙的に定義される変数を使うことでもうちょっと楽ができます。

// less
#vendor {
  .border-radius(@left-top; @right-top; @right-bottom; @left-bottom) {
    -moz-border-radius: @arguments;
    -webkit-border-radius: @arguments;
    border-radius: @arguments;
  }
}

div {
  #vendor > .border-radius(4px; 2px; 3px; 5px);
}

/* css */
div {
  -moz-border-radius: 4px 2px 3px 5px;
  -webkit-border-radius: 4px 2px 3px 5px;
  border-radius: 4px 2px 3px 5px;
}

ぶっちゃけてしまうと、,ないしは;が含まれていなければ一つの引数とみなすので、例のようにベンダプレフィックスを吸収するだけとかであれば、わざわざ複雑なことをせずにこれでいいと思います。

// less
#vendor {
  .border-radius(@param) {
    -moz-border-radius: @param;
    -webkit-border-radius: @param;
    border-radius: @param;
  }
}

div {
  // 半角スペースでわたす
  #vendor > .border-radius(4px 2px 3px 5px);
}

/* css */
div {
  -moz-border-radius: 4px 2px 3px 5px;
  -webkit-border-radius: 4px 2px 3px 5px;
  border-radius: 4px 2px 3px 5px;
}

ちょっと厄介なのが、パラメータとして,を含める可能性があるもの。

例えばbox-shadowですかね。下記のようにするとコンパイル時に「引数の数が一致するmixinが見つからないよ」と怒られてしまいます。

// less
#vendor {
  .box-shadow(@param) {
    -moz-box-shadow: @param;
    -webkit-box-shadow: @param;
    box-shadow: @param;
  }
}

div {
  // 複数の値を渡したいが…。
  #vendor > .box-shadow(0 0 4px #F00, 0 0 12px #000);
}

lessはさりげなく可変長引数...をサポートしているのでこれで…!と思うのですが、それだと,が消滅してしまいます。

// less
#vendor {
  .box-shadow(@param...) {
    -moz-box-shadow: @param;
    -webkit-box-shadow: @param;
    box-shadow: @param;
  }
}

div {
  #vendor > .box-shadow(0 0 4px #F00, 0 0 12px #000);
}

/* css */
div {
  -moz-box-shadow: 0 0 4px #ff0000 /* ここの","が消滅 */ 0 0 12px #000000;
  -webkit-box-shadow: 0 0 4px #ff0000 0 0 12px #000000;
  box-shadow: 0 0 4px #ff0000 0 0 12px #000000;
}

じゃあどうするのかですが、~を使ってエスケープしてしまいましょう。

// less
#vendor {
  .box-shadow(@param) {
    -moz-box-shadow: @param;
    -webkit-box-shadow: @param;
    box-shadow: @param;
  }
}

div {
  #vendor > .box-shadow(~"0 0 4px #F00, 0 0 12px #000");
}

/* css */
div {
  -moz-box-shadow: 0 0 4px #F00, 0 0 12px #000;
  -webkit-box-shadow: 0 0 4px #F00, 0 0 12px #000;
  box-shadow: 0 0 4px #F00, 0 0 12px #000;
}

可変長引数

説明が前後しますが、lessは...を使うことで可変長引数とすることができます。

引数が可変長引数のみの場合は識別子をつけなくても構いません。その場合は@argumentsで取得しましょう。

// less
.hoge(...) {
  border: @arguments;
}

.fuga(@color; @margin...) {
  color: @color;
  margin: @margin;
}

div {
  .hoge(solid, 10px, #00f);
  .fuga(#000, 0, 1px, 0, 0);
}

/* css */
div {
  border: solid 10px #0000ff;
  color: #000000;
  margin: 0 1px 0 0;
}

引数のデフォルト値

mixinの引数に@識別子: 値としておくことでデフォルト値を設定できます。

このデフォルト値は他の引数の値でも問題ありません。

// less
.hoge(@color: #000; @background-color: @color) {
  color: @color;
  background-color: @background-color;
}

div {
  .hoge();
}

span {
  .hoge(#fff);
}

table {
  .hoge(#fff, #f00);
}

/* css */
div {
  color: #000000;
  background-color: #000000;
}
span {
  color: #ffffff;
  background-color: #ffffff;
}
table {
  color: #ffffff;
  background-color: #ff0000;
}

名前付き引数

mixinを作っていると引数がめちゃめちゃ多くなったり、デフォルト値があったりなかったりするmixinを作成したくなることもあります。

そう言うときに便利なのが名前付き引数です。

// less
.hoge(@color: #000; @border; @background-color: @color) { // 第二引数にだけデフォルト値なし(こんなもん作らないようにしましょう)
  color: @color;
  border: @border;
  background-color: @background-color;
}

div {
  // 名前付き引数を使わない場合、
  // @colorもちゃんと引数で渡さなくてはならない
  .hoge(#000, solid 10px #00f); 
}

span {
  // 名前付き引数を使っているので
  // @colorと@background-colorはちゃんとデフォルト値になる
  .hoge(@border: solid 10px #00f);
}

table {
  // 名前付き引数の場合は順番がむちゃくちゃでもOK
  .hoge(
    @background-color: #f00,
    @color: #fff,
    @border: solid 10px #00f
  );
}

/* css */
div {
  color: #000000;
  border: solid 10px #0000ff;
  background-color: #000000;
}
span {
  color: #000000;
  border: solid 10px #0000ff;
  background-color: #000000;
}
table {
  color: #ffffff;
  border: solid 10px #0000ff;
  background-color: #ff0000;
}

ルールセットを引数として渡す

CSSのルールそのものを引数として渡すことも可能です。@keyframesとかで便利です。

// less
#vendor {
  .keyframes(@name, @prop) {
    @-moz-keyframes @name {
      @prop();
    }
    @-webkit-keyframes @name {
      @prop();
    }
    @-o-keyframes @name {
      @prop();
    }
    @keyframes @name {
      @prop();
    }
  }
  
  .animation(@param) {
    -moz-animation: @param;
    -webkit-animation: @param;
    -o-animation: @param;
    -ms-animation: @param;
    animation: @param;
  }
}

div {
  #vendor > .keyframes(hoge, {
    0% { background-color: #fff; }
    100% { background-color: #000 }
  });
  
  #vendor > .animation(hoge 5s ease 0 infinite alternate);
}

/* css */
div {
  -moz-animation: hoge 5s ease 0 infinite alternate;
  -webkit-animation: hoge 5s ease 0 infinite alternate;
  -o-animation: hoge 5s ease 0 infinite alternate;
  -ms-animation: hoge 5s ease 0 infinite alternate;
  animation: hoge 5s ease 0 infinite alternate;
}
@-moz-keyframes hoge {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: #000000;
  }
}
@-webkit-keyframes hoge {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: #000000;
  }
}
@-o-keyframes hoge {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: #000000;
  }
}
@keyframes hoge {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: #000000;
  }
}

mixinと変数のスコープ

mixin内で変数を宣言した場合、mixinを呼び出したスコープでその変数を使用することができます。

// less
.hoge {
  @base-color: #fff;
  color: @base-color;
}

div {
  .hoge();
  background-color: @base-color; // mixin内で宣言している変数を使用
}

/* css */
div {
  color: #ffffff;
  background-color: #ffffff;
}

mixinで呼び出した変数を書き換えてもそのスコープ内でしか値が変わりません。

また、変数を書き換えてもmixin内の値は変わりません。

// less
.hoge {
  @base-color: #fff;
  color: @base-color;
}

div {
  .hoge();
  @base-color: #000;
  background-color: @base-color;
}

span {
  .hoge();
  background-color: @base-color;
}

/* css */
div {
  color: #ffffff; /* @base-colorを書き換えたが、mixin内なので反映されない */
  background-color: #000000;
}
span {
  /* @base-colorを他スコープ(div)で書き換えられたが何も影響しない */
  color: #ffffff;
  background-color: #ffffff;
}

これを使うことで「mixinの返り値」を作成することができます。

// less
.average(@x; @y) {
  @average: ((@x + @y) / 2); // そう言えば演算関連まだ説明してないですね…。
}

div {
  .average(20px, 40px);
  margin: @average;
  padding: @average;
}

/* css */
div {
  margin: 30px;
  padding: 30px;
}

パターンマッチングとmixinのオーバーロード

同名かつ引数の数が違うmixinを作ることは可能なんですが、呼び出せるmixinはすべて呼び出すが基本ルールです。

// less
.hoge(@color) {
  color: @color;
}

.hoge(@margin) {
  margin: @margin;
}

div {
  .hoge(#fff); // @colorの方に行ってほしいが…
}

/* css */
div {
  color: #ffffff;
  margin: #ffffff; /* @marginの方も引数の数が一緒なので呼び出されてしまう */
}

上記の例を回避する方法はいくつかあります。まずはガードを使用し、引数の型をチェックする方法。

// less
.hoge(@color) when (iscolor(@color)) {
  color: @color;
}

.hoge(@margin) when (isnumber(@margin)) {
  margin: @margin;
}

div {
  .hoge(#fff);
}

/* css */
div {
  color: #ffffff;
}

キーワードを入れる方法もあります。

// less
.hoge(color, @color) {
  color: @color;
}

.hoge(margin, @margin) {
  margin: @margin;
}

div {
  .hoge(color, #fff);
}

span {
  .hoge(margin, 0);
}

/* css */
div {
  color: #ffffff;
}
span {
  margin: 0;
}

根本的な対処ではないですが、引数の数そのものが違ってもマッチングしないとみなしてくれます。

ただし、デフォルト値が設定されていると面倒なことになったりします。

// less
.hoge(@color) when (iscolor(@color)) {
  color: @color;
}

.hoge(@margin) when (isnumber(@margin)) {
  margin: @margin;
}

.hoge(@color, @border) {
  colol: @color;
  border: @border;
}

div {
  .hoge(#fff);
}

span {
  .hoge(#fff, solid 10px #0000ff);
}

/* css */
div {
  color: #ffffff;
}
span {
  color: #ffffff;
  border: solid 10px #0000ff;
}

ガード

ガードはかなり細かく設定できます。面倒ですが。

複数のガードを設定するときはandを使います。

// less
.hoge(@color, @margin) when (iscolor(@color)) and (isnumber(@margin)) {
  color: @color;
  margin: @margin
}

div {
  .hoge(#fff, 10px);
}

/* css */
div {
  margin: 10px;
  color: #ffffff;
}

否定する場合はnotをつけます。

// less
.hoge(@color, @margin) when (iscolor(@color)) and (isnumber(@margin)) {
  color: @color;
  margin: @margin
}

.hoge(@color, @border) when (iscolor(@color)) and not(isnumber(@border)) {
  color: @color;
  border: @border;
}

div {
  .hoge(#fff, 10px);
}

span {
  .hoge(#fff, solid 10px #0000ff);
}

/* css */
div {
  color: #ffffff;
  margin: 10px;
}
span {
  color: #ffffff;
  border: solid 10px #0000ff;
}

or条件を指定したい場合は,を使います。

// less
.hoge(@margin, @padding: @margin) when (isnumber(@margin)), (isnumber(@padding)) {
  margin: @margin;
  padding: @padding;
}

div {
  .hoge(10px);
}

span {
  .hoge(20px, fuga); // @marginがnumberなのでコンパイルが通る
}

/* css */
div {
  margin: 10px;
  padding: 10px;
}
span {
  margin: 20px;
  padding: fuga;
}

また、数値であれば<>も使えますし、文字列やキーワードの判定なんかもできます。

// less
.hoge(@margin, @padding) when (@padding <= 0px) {
  @padding: 5px;
  margin: @margin;
  padding: @padding;
}

.hoge(@margin, @padding) when (@padding > 10px) {
  @padding: 10px;
  margin: @margin;
  padding: @padding;
}

div {
  .hoge(10px, -1px);
}

span {
  .hoge(20px, 20px);
}

/* css */
div {
  margin: 10px;
  padding: 5px;
}
span {
  margin: 20px;
  padding: 10px;
}

// less
// !=は用意されてないのでnotを使う
.hoge(@keyword, @color) when not(@keyword=bg) {
  color: @color;
}

.hoge(@keyword, @color) when (@keyword=bg) {
  background-color: @color;
}

div {
  .hoge(hoge, #fff);
}

span {
  .hoge(bg, #000);
}

/* css */
div {
  color: #ffffff;
}
span {
  background-color: #000000;
}

どこにもマッチングしなかった場合のフォールバックとして、default()を使うこともできます。

この辺を使いこなせるようになると本当にオーバーロードっぽくなるのでぜひマスターしましょう。

// less
.hoge(@margin, @padding) when (default()) {
  margin: @margin;
  padding: @padding;
}

.hoge(@margin, @padding) when (@padding <= 0px) {
  .hoge(@margin, 5px); // when (default())
}

.hoge(@margin, @padding) when (@padding > 10px) {
  .hoge(@margin, 10px); // when (default())
}

div {
  .hoge(10px, -1px); // when (@padding <= 0px)
}

span {
  .hoge(20px, 20px); // when (@padding > 10px) 
}

table {
  .hoge(2px, 2px); // when (default())
}

/* css */
div {
  margin: 10px;
  padding: 5px;
}
span {
  margin: 20px;
  padding: 10px;
}
table {
  margin: 2px;
  padding: 2px;
}

まとめ

やはり長くなってしまった…。

次回はまだ説明してないあれこれを適当に説明していく予定です。