🛒

よく使うSassのmixinとfunctionのスニペットをまとめてみた

2020/12/25に公開
8

こんにちは。TAK(@tak_dcxi)です。

2020年最後のZennの投稿ということで、Web制作テンプレートの年末大掃除も兼ねて僕がよく使うSassのmixinとfunctionを厳選してまとめてみました。

Sassを使っている方でmixinとかfunctionをあまり利用してないという方は参考にしてみてください。

ブレークポイントの指定

おそらくmixinで最も利用されているのはレスポンシブでのブレークポイントの指定だと思います。

variables/_breakpoints.scss
$breakpoints: (
  'xs': (min-width: 0),
  'sm': (min-width: 576px),
  'md': (min-width: 768px),
  'lg': (min-width: 992px),
  'xl': (min-width: 1200px),
  'xxl': (min-width: 1400px)
) !default;
mixins/_responsive.scss
@mixin responsive($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media screen and #{inspect(map-get($breakpoints, $breakpoint))} {
      @content;
    }
  }
 
  // マップ型で定義されていない値が呼び出された時はエラーを返す
  @else {
    @error "指定されたブレークポイントは定義されていません。" + "指定できるブレークポイントは次のとおりです。 -> #{map-keys($breakpoints)}";
  }
}

ブレークポイントの変数はマップ型(キーと値のペアを定義できるデータタイプ)で行っています。マップ型で変数を定義しておくことで、@eachで繰り返しの処理をすることが可能になって色々とメリットが大きい。ブレークポイント用の変数を再利用してレスポンシブ対応のユーティリティクラスを生成できたりします。

Sass / Usage
body {
  font-size: 12px;
  
  @include responsive(md) {
    font-size: 14px;
  }
  
  @include responsive(xl) {
    font-size: 16px;
  }
}
CSS / Output
body {
  font-size: 12px;
}

@media screen and (min-width: 768px) {
  body {
    font-size: 14px;
  }
}

@media screen and (min-width: 1200px) {
  body {
    font-size: 16px;
  }
}

特にリクエストがなければブレークポイントの指定は次の2点を心がけています。

  • モバイルファーストで書く(min-widthで指定するようにする)
  • ブレークポイントはBootstrap 5のそれを参考にする

狭い画面幅を基準とすることで、スタイルの継承を促し、余計な上書きを避けることができてデスクトップファーストのそれより簡潔なレスポンシブ対応が可能になります。

ブレークポイントは有名なCSSのフレームワークに則っておけば安定だという考え。実際、モバイル(0〜767),タブレット(768〜991),デスクトップ(992〜)は2013年にリリースされたBootstrap 3の頃から変化していないあたりその安定性が伺えます。

「ブレークポイント多くない?」と言われそうですが、実装者はスマホやタブレットの横向きや巨大モニターなども意識してより多くのデバイス幅に対応できるようにレスポンシブ実装する必要があります。これくらい用意しておくのが妥当ではないかなと。

https://v5.getbootstrap.jp/docs/5.0/layout/breakpoints/

Hoverの指定

スマホやタブレット等のタッチデバイスではHoverイベントは見栄え的に邪魔になったり、デバイスによってはHoverが原因で二度タップしないとリンク先に飛べなかったり、ページ遷移後もHoverの状態が残る…といったデメリットが多いためオフにしたい。

デスクトップではHoverを有効にして、タッチデバイスではHoverを無効にする方法は

  • レスポンシブでデスクトップ用のスタイルにのみHoverを指定する
  • JSでUserAgentを見てHoverの有無を制御する
  • hoverメディアクエリを使う

あたりが考えられますが、1つ目の方法はタブレットビューとの境界線が曖昧なこと、2つ目の方法は新しい仕様のOSが現れた時に動かなくなる可能性があること(過去にiPadOSのデフォルトのUserAgentがmacOSと同じになったことで色々と余計な仕事が増えた経験あり)からhoverメディアクエリを使って出し分けをしたほうが手間もかからず安定しているという結論です。

ただ、hoverメディアクエリは肝心のデスクトップ向けブラウザ「Internet Explorer」では使えないため、そこでもHoverさせたいって案件だと別途IE用の記述も書かないといけないのが難点。

mixins/_hover.scss
@mixin hover($ie: false) {
  &:focus {
    @content;
  }
  
  @media (hover: hover) and (pointer: fine) {
    &:hover {
      @content;
    }
  }
  
  @if $ie {
    @at-root .ua-ie &:hover {
      @content;
    }
  }
}

一応引数でIE対応を行うかを指定しています。案件によりますが、今回はデフォルトではfalse。

JavaScript
!function(){
  const browser = window.navigator.userAgent.toLowerCase();
  const root = document.documentElement;

  // IEからアクセスされた場合はhtml要素に`class="ua-ie"`を付与する
  if (browser.indexOf('msie') > 0 || browser.indexOf('trident') > 0) {
    root.classList.add('ua-ie');
  } 
}();

IE対応する場合は上記のような判定用のJSを用意しておきましょう。UserAgentは将来的に廃止される予定ですが、開発終了しているIEにとってはUserAgent文字列廃止や新しいAPIは関係ないので、今まで通りUA文字列の判定で良いような気がしますね…。CSSハックはコードの治安が悪くなるので使わないようにしています。

Sass / Usage
.button--primary {
  @include hover() {
    opacity: .9;
  }
}

.button--secondary {
  @include hover(true) {
    opacity: .9;
  }
}
CSS / Output
.button--primary:focus {
  opacity: .9;
}

@media (hover: hover) and (pointer: fine) {
  .button--primary:hover {
    opacity: .9;
  }
}

.button--secondary:focus {
  opacity: .9;
}

@media (hover: hover) and (pointer: fine) {
  .button--secondary:hover {
    opacity: .9;
  }
}

.ua-ie .button--secondary:hover {
  opacity: .9;
}

:hoverと同時に:focusを指定している理由はTab移動のフォーカス時にもアクティブを分かりやすくするため。

フォーカスインジゲータがoutlineだけだと見にくい場合もあるので、outline以外でもフォーカスが当たっていることを強調させたほうが良いでしょう。

数値から単位を取り除く

次項以降のpx→remの計算とpx→vwの計算で使ったり、他のmixinやfunctionとの組み合わせにおいて汎用性が高いです。

functions/_strip-unit.scss
@function strip-unit($number) {
  @if type-of($number) == 'number' and not unitless($number) {
    @return $number / ($number * 0 + 1);
  }

  @return $number;
}
Sass / Usage
$value: strip-unit(32px);
// -> 32

px→remの計算

ルートのfont-sizeを62.5%で丸め込まずにpxをremに変換するfunction。

font-sizeにremを使う理由はこちらの記事で触れたとおり。

functions/_rem.scss
@function rem($px, $base: 16px) {
  $value: $px;

  // 単位がpx以外の場合は警告を出してそのまま返す
  @if (unit($px) != 'px') {
    @warn 'rem()の引数にpx以外の値を指定しても計算できません';
    @return $value;
  }

  $value: (strip-unit($px) / strip-unit($base)) * 1rem;

  @return $value;
}
  1. 第1引数に指定したいピクセル値を指定
  2. 第2引数に基準となるルートのfont-size値(デフォルトでは16px)を指定
Sass / Usage
.foo {
  font-size: rem(36px);
}

.bar {
  font-size: rem(36px, 12px);
}

.baz {
  font-size: rem(1.5em);
}
CSS / Output
.foo {
  font-size: 2.25rem;
}

.bar {
  font-size: 3rem;
}

.baz {
  font-size: 1.5em;
}

px→vwの計算

モバイルビューではどんな画面幅でもアスペクト比を維持したフルード的な実装をした方が多種多様なデバイスの横幅でもムラがなく表示できる(加えて新しいデバイスが登場しても慌てなくて済む)ので、基本単位をvwで実装することも多いです。

その時に一々四則計算を行うのは面倒なためfunctionを使います。これがないとリキッドレイアウトは厳しいって感想。

functions/_vw.scss
@function vw($px, $base: 375px) {
  $value: $px;

  // 単位がpx以外の場合は警告を出してそのまま返す
  @if (unit($px) != 'px') {
    @warn 'vw()の引数にpx以外の値を指定しても計算できません';
    @return $value;
  }

  $value: (strip-unit($px) / strip-unit($base)) * 100vw;

  @return $value;
}
  1. 第1引数に指定したいピクセル値を指定
  2. 第2引数に基準となる画面幅(デザインカンプの横幅)を指定
Sass / Usage
.foo {
  width: vw(40px);
}

.bar {
  width: vw(40px, 768px);
}

.baz {
  width: vw(50%);
}
CSS / Output
.foo {
  width: 10.66667vw;
}

.bar {
  width: 5.20833vw;
}

.baz {
  width: 50%;
}

なお、デスクトップで基本単位をvwで実装するのはデメリットが大きいので注意。

具体的には以下の2点。

  • 大型ディスプレイ(ウルトラワイドモニターなど)での表示がデカすぎて見にくくなる
  • ブラウザのズーム機能が機能しなくなってアクセシビリティ的によろしくない

ズーム機能の問題に関してはルートのfont-sizeにcalc()関数を使い、vwとpxなどの別の単位を組み合わせてremを介すれば解決はできます。

文字列からワードを検索して置換する

後続する@font-faceの設定で利用されているので、@font-faceを使う場合はこのfunctionも用意しておく。@font-faceの設定が不要ならこのfunctionも不要かもしれない。

functions/_str-replace.scss
@function str-replace($string, $search, $replace: '') {
  $index: str-index($string, $search);

  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
  }

  @return $string;
}

次項の@font-faceの設定のmixinと同じく、Jonathan Neal氏のmixinを拝借しています。

https://gist.github.com/jonathantneal/d0460e5c2d5d7f9bc5e6#file-_mixins-scss

  1. 第1引数に検索対象を指定
  2. 第2引数に置換したいワードを指定
  3. 第3引数に置換後のワードを指定
Sass / Usage
.message {
  &::before {
    // 疑似要素内で改行を行うために' 'を\Aに置換する
    content: str-replace('今年もありがとうございました。 来年もよろしく。', ' ', '\A');
    white-space: pre;
  }
}
CSS / Output
.message::before {
  content: '今年もありがとうございました。\a来年もよろしく。';
  white-space: pre;
}

@font-faceの設定

@font-faceを用いてダウンロードしたWebフォントを簡単に扱えるようにしたmixin。

mixins/_font-face.scss
@mixin font-face($family, $path, $weight: normal, $style: normal, $exts: eot woff2 woff ttf svg) {
  $src: null;

  $extmods: (
    eot: '?',
    svg: '#' + str-replace($family, ' ', '_'),
  );

  $formats: (
    otf: 'opentype',
    ttf: 'truetype',
  );

  @each $ext in $exts {
    $extmod: if(map-has-key($extmods, $ext), $ext + map-get($extmods, $ext), $ext);
    $format: if(map-has-key($formats, $ext), map-get($formats, $ext), $ext);
    $src: append($src, url(quote($path + "." + $extmod)) format(quote($format)), comma);
  }

  @font-face {
    font-family: quote($family);
    font-style: $style;
    font-weight: $weight;
    src: $src;
  }
}

前項の文字列からワードを検索して置換するfunctionと同じく、Jonathan Neal氏のmixinを拝借しています。

https://gist.github.com/jonathantneal/d0460e5c2d5d7f9bc5e6#file-_mixins-scss

  1. 第1引数にフォント名(font-family)を指定
  2. 第2引数にフォントファイルへの相対パスを指定
  3. 第3引数に文字の太さ(font-weight)を指定
  4. 第4引数に書体(font-style)を指定
  5. 第5引数に対応するフォントファイルの拡張子を指定
Sass / Usage
@include font-face('Lora', 'fonts/Lora', 400, italic, ttf);

@include font-face('Samplinal', 'fonts/Samplinal', bold, normal, eot woff2 woff);
CSS / Output
@font-face {
  font-family: 'Lora';
  font-style: italic;
  font-weight: 400;
  src: url('fonts/Lora.ttf') format('truetype');
}

@font-face {
  font-family: 'Samplinal';
  font-style: normal;
  font-weight: bold;
  src: url('fonts/Samplinal.eot?') format('eot'), url('fonts/Samplinal.woff2') format('woff2'), url('fonts/Samplinal.woff') format('woff');
}

表示行数の制御

文章の行数を制限して三点リーダーを表示するmixin。

表示行数の制御を行うプロパティである-webkit-line-clampは2009年にリリースされたdisplay: flexの前身となるdisplay: -webkit-boxが持つ機能。とっくの昔にW3Cの仕様からは消えたはずなのですが、どういうわけか令和になってからFirefoxでも利用できるようになりました。現在はIE以外の主要ブラウザで対応済み。

mixins/_line-clamp.scss
@mixin line-clamp($count: 3) {
  // 引数が数値以外だったらエラーを返す
  @if type-of($count) != 'number' {
    @error 'line-clampの引数は必ず数値で指定してください';
  }

  @if $count == 1 {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  } @else {
    display: -webkit-box;
    -webkit-line-clamp: $count;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}

引数に何行で省略するかを指定します。デフォルトでは3行ですがお好みで。

1行の場合はtext-overflow: ellipsiswhite-space: nowrapで省略を行う。これはIEでも利用可能。

Sass / Usage
.foo {
  @include line-clamp(1);
}

.bar {
  @include line-clamp(4);
}
CSS / Output
.foo {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bar {
  -webkit-box-orient: vertical;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  overflow: hidden;
}

IEで利用できる複数行で三点リーダーを表示して省略する方法も一応はあります。このような感じ。

ただ、背景色を指定しているため背景が画像やパターンの場合は利用ができません。また場合によっては文字が途切れて三点リーダーが表示されてしまいます。癖が強すぎるのでJS使えるならJS使ったほうが良いかもしれません。

IE Buster導入してIE対応から逃げたい。

■ line-clampについて参考になりそうな記事

https://lopan.jp/omit-text/

clearfix

floatを解除するアレ。clearfixのユーティリティクラスを作るよりもmixinを用意してCSSのコードの方で明確化した方がいい(個人的感想)

mixins/_clearfix.scss
@mixin clearfix() {
  &::after {
    clear: both;
    content: '';
    display: block;
  }
}
Sass / Usage
.list {
  @include clearfix();
  
  & > .item {
    float: left;
  }
}
CSS / Output
.list::after {
  clear: both;
  content: '';
  display: block;
}

.list > .item {
  float: left;
}

この時代にfloatで横並びを行うってレアだと思っていたけれど、近頃fieldset要素の中のレイアウトを組む時に一部のブラウザではdisplay: flexdisplay: gridも効かないからfloatで頑張った思い出があります。

floatはオワコンか?議論ってちょくちょくありますけど保守案件とか頻出頻度めちゃくちゃ高いし、あくまで新規で横並びに使うのは古いってだけであって、ある程度知識はあったほうが良いですよ。それと、floatの本来の使い方は「回り込みレイアウト」で、これはfloat以外では実現ができないのでそういったレイアウトを組む時はfloatの出番はやってきます。あまり見かけることはない気がしますが。

こういった雑誌的なレイアウトを組む時はfloatが必要になりますね。

ちなみにfloatの解除は大抵の場合overflow: hiddenで事足ります。一応保険として…。

■ floatについて参考になりそうな記事

https://zarigani-design-office.com/blog/is_float_owacon/

object-fit

個人的にはPolyfill導入してでも使いたいIE非対応CSSではTier上位のobject-fit

object-fitだけならmixin導入する必要は無いのだけれど、Polyfill導入する時に指定しなければいけないfont-familyの指定が地味に面倒なのでmixin化してます。

mixins/_object-fit.scss
@mixin object-fit($value: fill, $position: null) {
  object-fit: $value;

  @if $position {
    object-position: $position;
    font-family: 'object-fit: #{$value}; object-position: #{$position}', sans-serif;
  }
  @else {
    font-family: 'object-fit: #{$value}', sans-serif;
  }
}
  1. 第1引数にobject-fitの値を指定(デフォルト値は初期値のfillを指定)
  2. 第2引数に使う必要があるのならobject-positionの値を指定
Sass / Usage
.photo__image {
  &.-image1 {
    @include object-fit(cover);
  }
  
  &.-image2 {
    @include object-fit(contain, top center);
  }
}
CSS / Output
.photo__image.-image1 {
  font-family: 'object-fit: cover', sans-serif;
  object-fit: cover;
}

.photo__image.-image2 {
  font-family: 'object-fit: contain; object-position: top center', sans-serif;
  object-fit: contain;
  object-position: top center;
}

object-fitのPolyfillの「object-fit-images」と:focus-visibleのPolyfillはどの案件でも導入しているような気がする。

ちなみにobject-fitは置換要素をどうするか?のプロパティなのでvideoとかにも使えます。

■ object-fit-images

https://github.com/fregante/object-fit-images

■ object-fitで参考になりそうな記事

https://www.webcreatorbox.com/tech/object-fit
https://www.codegrid.net/articles/2016-css-object-1/

画像などを縦横比を維持したままレスポンシブ対応

画像やGoogle Map、YouTubeの埋め込みなどを縦横比を維持したままレスポンシブ対応したいって場合は「paddingの上下の値は%指定だと親要素の横幅が基準となる」という仕様を利用してpaddingで高さを作ることが多いです。

こういった裏技的なテクニックはただスタイル指定しているだけだと裏技についてよく知らない他の人が見た時に「このpaddingは何だ?」ってなることも多いので、mixinを介して役割を明確化して実装者の意図を表現する。局所的なものならコメント残せばいいですが、汎用的なものだといちいちコメント書くの大変ですからね。あと、計算苦手すぎて縦横どっちでどっちを割るのかたまに分からなくなるという情けない個人的理由もある。

mixins/_aspect-ratio.scss
@mixin aspect-ratio($width, $height) {
  position: relative;
 
  &::before {
    content: '';
    float: left;
    padding-top: ($height / $width) * 100%;
  }
  
  &::after {
    content: '';
    display: block;
    clear: both;
  }
  
  & > :first-child {
    height: 100%;
    left: 0;
    position: absolute;
    top: 0;
    width: 100%;
  }
}
  1. 第1引数に横幅(width)を指定
  2. 第2引数に縦幅(height)を指定

ボックスから溢れるほどの長文などが含まれている場合、padding-topだけだとはみ出したコンテンツが隠れてしまうので上のコードのようにfloatでやりくりしたほうが良いらしいです。

https://css-tricks.com/aspect-ratio-boxes/

Sass / Usage
.photo {
  // 画像を16:9にトリミングして縦横比を常に保つ
  @include aspect-ratio(16, 9);

  & > .image {
    @include object-fit(cover);
  }
}
HTML / Usage
<figure class="photo">
  <img class="image" 
       src="sample.jpg" 
       alt="画面幅に合わせて可変させたい画像" 
       width="480" 
       height="297" 
       decoding="async">
</figure>
CSS / Output
.photo {
  position: relative;
}

.photo::before {
  content: '';
  float: left;
  padding-top: 56.25%;
}

.photo::after {
  content: '';
  display: block;
  clear: both;
}

.photo > :first-child {
  height: 100%;
  left: 0;
  position: absolute;
  top: 0;
  width: 100%;
}

.photo > .image {
  object-fit: cover;
  font-family: 'object-fit: cover', sans-serif;
}

YouTubeの場合は共有から出力されるiframeのHTMLにwidth属性とheight属性が指定されているのでその値を引数にセットすればOKですね。大抵の動画は16:9なので@include aspect-ratio(16, 9)でも良いかもしれない。

ちなみに2020年時点では全ブラウザで実装されていませんが、将来的にはaspect-ratio: 16 / 9の1行だけで画面幅に合わせて可変させることが容易にできるそうです。

三角形の作成

吹き出しを作ったりボタンに飾り付けをしたりする時にborderで三角形を作るのは日常茶飯事ですが、一々指定するには記述量が多いのと、如何せん裏技感のある指定方法なので役割を明確化するためにもmixin化しています。

mixins/_triangle.scss
@mixin triangle($direction, $width, $height, $color: currentColor) {
  // 定義されている方向が指定されている、もしくは方向が指定されていなかったらエラーを返す
  @if not index(upward rightward downward leftward upper-left upper-right lower-right lower-left, $direction) {
    @error 'triangle()の方向は"upward","rightward","downward","leftward","upper-left","upper-right","lower-right","lower-left"から指定してください';
  }

  border-style: solid;
  height: 0;
  width: 0;

  @if $direction == upward {
    border-color: transparent transparent $color transparent;
    border-width: 0 ($width / 2) $height ($width / 2);
  }

  @if $direction == rightward {
    border-color: transparent transparent transparent $color;
    border-width: ($height / 2) 0 ($height / 2) $width;
  }

  @if $direction == downward {
    border-color: $color transparent transparent transparent;
    border-width: $height ($width / 2) 0 ($width / 2);
  }

  @if $direction == leftward {
    border-color: transparent $color transparent transparent;
    border-width: ($height / 2) $width ($height / 2) 0;
  }

  @if $direction == upper-left {
    border-color: $color transparent transparent transparent;
    border-width: $height $height 0 0;
  }

  @if $direction == upper-right {
    border-color: transparent $color transparent transparent;
    border-width: 0 $height $height 0;
  }

  @if $direction == lower-right {
    border-color: transparent transparent $color transparent;
    border-width: 0 0 $height $height;
  }

  @if $direction == lower-left {
    border-color: transparent transparent transparent $color;
    border-width: $height 0 0 $height;
  }
}
  1. 第1引数に方向を指定。上向き(upward)、右向き(rightward)、下向き(downward)、左向き(leftward)、左上(upper-left)、右上(upper-right)、右下(lower-right)、左下(lower-left)の8方向から選択します
  2. 第2引数に三角形の横幅を指定します
  3. 第3引数に三角形の縦幅を指定します
  4. 第4引数に三角形の色を指定します。デフォルトは現在の文字色(currentColor)
Sass / Usage
.example {
  position: relative;

  &::before {
    content: '';
    display: inline-block;
    position: absolute;
  }
  
  &[data-direction=upward] {
    &::before {
      @include triangle(upward, 20px, 15px, #cd5c5c);

      bottom: 100%;
      left: calc(50% - 10px);
    }
  }
  
  &[data-direction=upper-left] {
    &::before {
      @include triangle(upper-left, 12px, 12px);

      left: 6px;
      top: 6px;
    }
  }
}
CSS / Output
.example {
  position: relative;
}

.example::before {
  content: '';
  display: inline-block;
  position: absolute;
}

.example[data-direction=upward]::before {
  border-style: solid;
  height: 0;
  width: 0;
  border-color: transparent transparent #cd5c5c transparent;
  border-width: 0 10px 15px 10px;
  bottom: 100%;
  left: calc(50% - 10px);
}

.example[data-direction=upper-left]::before {
  border-style: solid;
  height: 0;
  width: 0;
  border-color: currentColor transparent transparent transparent;
  border-width: 12px 12px 0 0;
  left: 6px;
  top: 6px;
}

表示サンプルはこちら

borderで三角形を作るのは頻出頻度が高いので初学者の方は是非とも知っておくと良いと思います。

3次ベジェ曲線のイージング

アニメーション実装においてイージングは至極重要なのですが、用意されているイージングのlinear,ease,ease-in,ease-out,ease-in-outだけではメリハリがつかずに物足りないってことはよくあることです。そういった時に3次ベジェ曲線でイージングを設定できるcubic-bezierを使うのですが、一々自分で調整するのは面倒なのでRobert Pennerのeasing関数を利用することが多いです。

実際の動きは下記のWebサイトで確認できます。Elastic系とBounce系はcubic-bezierでは再現できないので、anime.jsなどのJSプラグインを利用する必要があります。
https://matthewlein.com/ceaser/

variables/_easings.scss
$easings: (
  // sine
  in-sine: cubic-bezier(.47, 0, .745, .715),
  out-sine: cubic-bezier(.39, .575, .565, 1),
  in-out-sine: cubic-bezier(.445, .05, .55, .95),
  
  // quad
  in-quad: cubic-bezier(.55, .085, .68, .53),
  out-quad: cubic-bezier(.25, .46, .45, .94),
  in-out-quad: cubic-bezier(.455, .03, .515, .955),

  // cubic
  in-cubic: cubic-bezier(.55, .055, .675, .19),
  out-cubic: cubic-bezier(.215, .61, .355, 1),
  in-out-cubic: cubic-bezier(.645, .045, .355, 1),

  // quart
  in-quart: cubic-bezier(.895, .03, .685, .22),
  out-quart: cubic-bezier(.165, .84, .44, 1),
  in-out-quart: cubic-bezier(.77, 0, .175, 1),

  // quint
  in-quint: cubic-bezier(.755, .05, .855, .06),
  out-quint: cubic-bezier(.23, 1, .32, 1),
  in-out-quint: cubic-bezier(.86, 0, .07, 1),

  // expo
  in-expo: cubic-bezier(.95, .05, .795, .035),
  out-expo: cubic-bezier(.19, 1, .22, 1),
  in-out-expo: cubic-bezier(1, 0, 0, 1),

  // circ
  in-circ: cubic-bezier(.6, .04, .98, .335),
  out-circ: cubic-bezier(.075, .82, .165, 1),
  in-out-circ: cubic-bezier(.785, .135, .15, .86),

  // back
  in-back: cubic-bezier(.6, -.28, .735, .045),
  out-back: cubic-bezier(.175, .885, .32, 1.275),
  in-out-back: cubic-bezier(.68, -.55, .265, 1.55)
) !default;
functions/_ease.scss
@function ease($easing) {
  @if map-has-key($easings, $easing) {
    @return map-get($easings, $easing);
  }

  // 定義されていないイージングが指定された時は警告を出して初期値のeaseを返す
  @warn 'イージング"#{$easing}"は定義されていません';
  @return ease;
}
Sass / Usage
.foo {
  animation: animation-foo 1s ease(out-expo) .4s both;
}

.bar {
  animation: animation-bar 1s ease(in-out-circ) .4s both;
}

.baz {
  animation: animation-baz 1s ease(a) .4s both;
}
CSS / Output
.foo {
  animation: animation-foo 1s cubic-bezier(.19, 1, .22, 1) .4s both;
}

.bar {
  animation: animation-bar 1s cubic-bezier(.785, .135, .15, .86) .4s both;
}

.baz {
  animation: animation-baz 1s ease .4s both;
}

アニメーションのイージングをどうするか?を実装者に委ねられた場合、迷ったら最初はease-out系を試すと良いかと思われます。というのも物理法則的に動きが自然だから。最初はeaseOutExpoあたりを試してます。逆にease-in系は動きが不自然だから基本的に利用頻度は少ないです(個人的感想)

function使わなくてもグローバル変数で事足りるのは内緒。

■ イージングで参考になりそうな記事

https://ics.media/entry/18730/

スクリーンリーダー向けの隠しコンテンツ

主にアクセシビリティ対応。ビジュアル的には隠したいけれどスクリーンリーダーでは読ませたい見出しやテキストに指定します。他にもスキップリンクの実装や、デザイン上の情報設計が破綻していてHTMLの構造をきちんと書く場合や、ラジオボタンやチェックボックスを装飾する時に元のinputを隠すためなどに使うことが多い。(inputをdisplay: noneしてはいけない理由はこちらの記事で解説しています)

mixins/_visually-hidden.scss
@mixin visually-hidden() {
  // a11y-css-resetから引用
  // https://github.com/mike-engel/a11y-css-reset
  border: 0 !important;
  clip: rect(0 0 0 0) !important;
  clip-path: inset(50%) !important;
  height: 1px !important;
  margin: -1px !important;
  overflow: hidden !important;
  padding: 0 !important;
  position: absolute !important;
  white-space: nowrap !important;
  width: 1px !important;
}

既存のスタイルを打ち消して確実なスタイルの適用が必要とされるために!importantを許容します。 ユーティリティクラスとしても用意しておくと良いかもしれない。

所謂隠しテキストなのでSEO的に大丈夫?(=スパム扱いされないか?)って意見もよく聞きますが、アプローチは違えどGoogle Developersブログでも紹介されているやり方なので問題はないかと思います。もちろん、10年前くらいに流行ったキーワード詰め込み隠しテキストの実装はスパム扱いされても文句はありませんし、何よりスクリーンリーダーで支離滅裂な読み上げが行われるので絶対に止めましょう。

https://developers.google.com/web/fundamentals/accessibility/semantics-aria/hiding-and-updating-content?hl=ja

おわりに

Sassのmixinやfunctionは複雑なスタイル指定を手軽に行えたり、似たような記述をする手間が省けたりと使いこなせば大幅な作業効率化に繋がるだけでなく、関数の命名によって実装者の意図が明白になり他の方が読んだ時に分かりやすいコードを書くことにも繋がります。

ただ、便利だからといって無闇矢鱈と使うとかえって抽象化し過ぎのコードになってしまい、他人だけでなく自分でさえよく分からなくなってしまう恐れもあります。

本当にそのmixinやfunctionが必要か?flexとかabsoluteとか本当にmixin化する必要があるか?などはよく考えて取り入れるようにしましょう。

mixinとfunctionのご利用は計画的に。

Discussion

ゆうてんゆうてん

IE非対応の制御、 @supportsクエリは検討されなかったんですか?

TAKTAK

@supportsそのものがIE非対応なため、@supportsで指定のプロパティが対応しているか否かでスタイルを出し分けたところで「IE対応のための処理」自体は別途地道にやる必要があり、コストそのものは変わらない印象です。記事中で触れられているIE非対応のプロパティはIE以外のモダンブラウザで利用可能なので、@supportsが使えてかつ指定のプロパティが利用できないブラウザのために出し分けを行う必要性も感じられず。

記事中で触れたIE非対応の制御において@supportsを使えばよりベターなコードが書けるのであれば、そこは参考のためにもご教授願いたいです…。

YukiYuki

TACさんの記事とても有料級で参考になりました😭
因みに普段、コンパイルは何でおこなっておりますか?

TAKTAK

Dart Sassを使うために個人的なプロジェクトでは現在はwebpackでコンパイルしてます。