🌼

Sassで汎用的なメディアクエリを作成する

2022/08/05に公開約6,100字

なぜ?

今まで@include sp() {}@include pc() {}みたいな形でSass mixinを作ってきており、細かなブレークポイントの指定が入ると(例:@media (min-width: 320px) and (max-width: 500px) {})、いちいち書くのが面倒になっていた。
@include mq(320, 500) {}@include mq(tablet) {}のように書ければ楽だろうなと考え、実装ができそうだったのでしてみることに。

完成品

@use "sass:meta";
@use "sass:list";

$_all-media-type: (all, print, screen, speech);
$_all-template: (pc, tablet);
$_default-media-type: all;
$_default-template: pc;
$_mobile: 599;
$_tablet: 1023;

@mixin mq($min: 0, $max: 0, $template: "", $type: $_default-media-type) {
  @if meta.type-of($min) == string {
    $template: $min;
    $min: 0;
    @if meta.type-of($max) == string {
      $type: $max;
      $max: 0;
    }
  } @else if $min != 0 and $max != 0 and $template != "" {
    $type: $template;
  } @else if meta.type-of($min) == number and meta.type-of($max) == string {
    $type: $max;
    $max: 0;
  }

  @if meta.type-of($min) != number {
    @error Use number type for "$min", value is "#{$min}";
  }
  @if meta.type-of($max) != number {
    @error Use number type for "$max", value is "#{$max}";
  }
  @if meta.type-of($type) != string {
    @error Use string type for "$type", value is "#{$type}";
  }
  @if not list.index($_all-media-type, $type) {
    @error Use media-type $_all-media-type, value is "#{$type}";
  }
  @if $min == 0 and $max == 0 {
    @if meta.type-of($template) == string {
      @if $template == "" {
        $template: $_default-template;
      }
      @if not list.index($_all-template, $template) {
        @error Use template $_all-template value is "#{$template}";
      }
      @if $template == pc {
        @media #{$type} and (min-width: #{($_tablet + 1) * 1px}) {
          @content;
        }
      } @else if $template == tablet {
        @media #{$type} and (min-width: #{($_mobile + 1) * 1px}) and (max-width: #{$_tablet * 1px}) {
          @content;
        }
      }
    }
  } @else if $min != 0 and $max != 0 {
    @if $min >= $max {
      @error "$max" must be greater than "$min";
    }
    @media #{$type} and (min-width: #{$min * 1px}) and (max-width: #{$max * 1px}) {
      @content;
    }
  } @else {
    @if $template != "" {
      @error Use "@mq(string)";
    }
    @if $min != 0 {
      @media #{$type} and (min-width: #{$min * 1px}) {
        @content;
      }
    } @else if $max != 0 {
      @media #{$type} and (max-width: #{$max * 1px}) {
        @content;
      }
    }
  }
}

使い方

@include mq() {} // @media all and (min-width: 1024px) {}
@include mq(tablet) {} // @media all and (min-width: 600px) and (max-width: 1023px) {}
@include mq(320, 600) {} // @media all and (min-width: 320px) and (max-width: 600px) {}
@include mq(320) {} // @media all and (min-width: 320px) {}
// 引数指定の方法でもOK
@include mq($max: 600) {} // @media all and (max-width: 600px) {}
@include mq(0, 600) {} // @media all and (max-width: 600px) {}

// メディアタイプを変える
@include mq(tablet, print) {} // @media print and (min-width: 600px) and (max-width: 1023px) {}
@include mq($type: print) {} // @media print and (min-width: 1024px) {}
@include mq(320, 600, print) {} // @media print and (min-width: 320px) and (max-width: 600px) {}
@include mq(320, print) {} // @media print and (min-width: 320px) {}

解説

随所でmeta.type-ofによって引数の型を調べて分岐させている。

参考

https://sass-lang.com/documentation/modules/meta#type-of

最初のブロックでは

@if meta.type-of($min) == string {
  $template: $min;
  $min: 0;
  @if meta.type-of($max) == string {
    $type: $max;
    $max: 0;
  }
} @else if $min != 0 and $max != 0 and $template != "" {
  $type: $template;
} @else if meta.type-of($min) == number and meta.type-of($max) == string {
  $type: $max;
  $max: 0;
}

以下のようなパターンで引数指定された時を想定しており

@include mq(tablet) {}
@include mq(320, print) {}
@include mq(320, 600, print) {}

引数に対してどのような型で渡されたか、初期値かどうかの2択で判定している。
@include mq(tablet) {}のような指定の仕方だった場合、$min: tabletの渡され方になるので、$templateへ値を渡しつつ、$minを初期化している。
@include mq(tablet, print) {}のような場合、$max: printも渡されるため、$max$typeへ渡すようにしている。
@include mq(print, tablet) {}の場合、$typeにtabletが入ってしまうため、下記チェックでメディアタイプの指定エラーになる。
次のブロックでは基本的な引数の型エラーを出すようにしている。

@if meta.type-of($min) != number {
  @error Use number type for "$min", value is "#{$min}";
}
@if meta.type-of($max) != number {
  @error Use number type for "$max", value is "#{$max}";
}
@if meta.type-of($type) != string {
  @error Use string type for "$type", value is "#{$type}";
}
@if not list.index($_all-media-type, $type) {
  @error Use media-type $_all-media-type, value is "#{$type}";
}
@include mq($min: tablet) {}

などは型エラーとなる。
list.indexlist.index(heystack, needle)と使用され、heystack内にneedleがあれば、indexを返すといった関数になります。
$_all-media-typeには

$_all-media-type: (all, print, screen, speech);

指定できるメディアタイプを入れており、こちらの配列から値を探している。
最後のブロックでは

@if $min == 0 and $max == 0 {
  @if meta.type-of($template) == string {
    @if $template == "" {
      $template: $_default-template;
    }
    @if not list.index($_all-template, $template) {
      @error Use template $_all-template value is "#{$template}";
    }
    @if $template == pc {
      @media #{$type} and (min-width: #{($_tablet + 1) * 1px}) {
        @content;
      }
    } @else if $template == tablet {
      @media #{$type} and (min-width: #{($_mobile + 1) * 1px}) and (max-width: #{$_tablet * 1px}) {
        @content;
      }
    }
  }
} @else if $min != 0 and $max != 0 {
  @if $min >= $max {
    @error "$max" must be greater than "$min";
  }
  @media #{$type} and (min-width: #{$min * 1px}) and (max-width: #{$max * 1px}) {
    @content;
  }
} @else {
  @if $template != "" {
    @error Use "@mq(string)";
  }
  @if $min != 0 {
    @media #{$type} and (min-width: #{$min * 1px}) {
      @content;
    }
  } @else if $max != 0 {
    @media #{$type} and (max-width: #{$max * 1px}) {
      @content;
    }
  }
}
  • $min$maxが初期値の場合(例:@include mq(tablet) {}など)、$templateを使用して決まったメディアクエリを適用する。
    • メディアタイプと同様に使えるテンプレートを規制している。
  • $min$maxに数値がある場合(例:@include mq(320) {}@include mq(320, 600) {}など)、数値を利用してメディアクエリを適用する。

良ければ使ってみて感想をください!

Discussion

ログインするとコメントできます