Closed2

ホバーアニメーションをモーション単位で管理するmixin

のっくんのっくん

作った経緯

  • ホバーアニメーションを個別で書いてたけど、サイト全体で記述を共通化してアニメーションの雰囲気合わせたいと思ったから。
  • ホバーアニメーションをつける場合にモジュール単位ではなく、モーション単位で管理すれば使い回ししやすい。特に複数の要素が集まってできた複合要素に対して対応が簡単になる。

使い方

  • ホバーアニメーションを付加したい要素に対してmixinを呼び出し。
  • テキストのカラー変更などホバーアニメーションによって動作が違うものは変更したいプロパティの値をmixinの引数に設定する。
  • 子要素に付加したい場合は、子要素のセレクタをmixinの引数に設定する。

課題点

  • 一つの要素に複数のアクションをつける場合にtransitionが後発で設定したアニメーションのものに上書きされる。
  • 引数が複数設定している場合、一つ目の引数が呼び出し時に必須となるため記述が面倒。
hover.scss
/// @group color
$color-red: #a00024;

/// @group easing
$easeDefault: cubic-bezier(0, -0.09, 0.1, 0.93);
$ease: $easeDefault;
$time: 0.4s;

/// @group media query
$media-pc: 'screen and (min-width: #{c-break.$breakpoint})';
@mixin media-pc {
  @media #{$media-pc} {
    @content;
  }
}

// ホバーの設定ここから
/// @group motion
/// ホバーでカラー変更
@mixin hover-color($color: $color-red, $child: null) {
  @if $child != null {
    #{$child} {
      transition: color $time $ease;
    }
    @include a-media.media-pc {
      &:hover {
        #{$child} {
          color: $color;
          transition: color $time $ease;
        }
      }
    }
  }
  @else {
    transition: color $time $ease;
    &:hover {
      @include media-pc {
        color: $color;
      }
    }
  }
}

/// @group motion
/// ホバーでスケールアップ
@mixin hover-scale($child: null) {
  @if $child != null {
    #{$child} {
      transition: transform $time $ease;
    }
    @include media-pc {
      &:hover {
        #{$child} {
          transform: scale(1.05);
          transition: transform $time $ease;
        }
      }
    }
  }
  @else {
    transition: transform $time $ease;
    &:hover {
      @include media-pc {
        transform: scale(1.05);
      }
    }
  }
}

のっくんのっくん

課題点

  • 一つの要素に複数のアクションをつける場合にtransitionが後発で設定したアニメーションのものに上書きされる。
  • 引数が複数設定している場合、一つ目の引数が呼び出し時に必須となるため記述が面倒。

課題点に対応した。

コード全体

hover.scss
/// @group color
$color-red: #a00024;

/// @group easing
$easeDefault: cubic-bezier(0, -0.09, 0.1, 0.93);
$ease: $easeDefault;
$time: 0.4s;

/// @group media query
$media-pc: 'screen and (min-width: 768px)';
@mixin media-pc {
  @media #{$media-pc} {
    @content;
  }
}

/// @group motion
/// モーションごとにtransitionプロパティを設定、同一要素に複数アニメーションを適用する場合はカンマ区切りで追加する。
@mixin transition($properties: ()) {
  $transiton-in: '';
  $transiton-out: '';
  @each $property in $properties {
    @if $transiton-in != '' { $transiton-in: $transiton-in + ', '; }
    @if $transiton-out != '' { $transiton-out: $transiton-out + ', '; }

    @if $property == 'color' {
      $transiton-in: $transiton-in+'color #{a-ease.$time} #{a-ease.$ease}';
      $transiton-out: $transiton-out+'color #{a-ease.$time} #{a-ease.$ease}';
    }
    @else if $property == 'scale' {
      $transiton-in: $transiton-in+'transform 0.3s #{a-ease.$ease}';
      $transiton-out: $transiton-out+'transform 0.6s #{a-ease.$ease}';
    }
  }
  @include a-media.media-pc {
    transition: #{$transiton-out};
    &:hover {
      transition: #{$transiton-in};
    }
  }
}

/// @group motion
/// スケールアップ
@mixin hover-scale($args...) {
  $option: meta.keywords($args);
  $child: if(map-get($option, 'child'), map-get($option, 'child'), null);
  $value: if(map-get($option, 'value'), map-get($option, 'value'), 1.2);
  @if $child != null {
    #{$child} {
      @include transition('scale');
    }
    @include a-media.media-pc {
      &:hover {
        #{$child} {
          transform: scale($value);
        }
      }
    }
  }
  @else {
    @include transition('scale');
    @include a-media.media-pc {
      &:hover {
        transform: scale($value);
      }
    }
  }
}

/// @group motion
/// カラー変更
@mixin hover-color($args...) {
  $option: meta.keywords($args);
  $child: if(map-get($option, 'child'), map-get($option, 'child'), null);
  $value: if(map-get($option, 'value'), map-get($option, 'value'), $color-red);
  @if $child != null {
    #{$child} {
      @include transition('color');
    }
    @include a-media.media-pc {
      &:hover {
        #{$child} {
          color: $value;
        }
      }
    }
  }
  @else {
    @include transition('color');
    @include a-media.media-pc {
      &:hover {
        color: $value;
      }
    }
  }
}

/// @group motion
@mixin hover($properties: null, $options: null) {
  @each $property in $properties {
    $c: null;
    $v: null;
    @if $options != null {
      @each $key, $option in $options {
        @if $key == $property {
          $c: if(map-get($option, 'child'), map-get($option, 'child'), null);
          $v: if(map-get($option, 'value'), map-get($option, 'value'), null);
        }
      }
    }
    @if $property == 'scale' { @include hover-scale($child: $c, $value: $v); }
    @if $property == 'color' { @include hover-color($child: $c, $value: $v); }
  }
}


// 呼び出し方
.target-01 {
  // カラー変更
  // 要素自体にアニメーションをつけたい時
  // カラーはデフォルトの値
  @include hover('color');
}
.target-02 {
  // カラー変更
  // 子要素にアニメーションをつけたい時
  // カラーの値を変更
  @include hover('color', (
    color: ( 
      child: '.child',
      value: green
    )
  ));
}
.target-03 {
  // スケールアップ+カラー変更
  // 要素自体にアニメーションをつけたい時
  // カラーの値を変更
  @include hover(('scale', 'color'));
  // 注意!同じ要素に複数のアニメーションをつける場合、transitionプロパティがかぶるのでtransition mixinで個別指定が必要
  @include transition(('color', 'scale'));
}
.target-04 {
  // スケールアップ+カラー変更
  // 子要素にアニメーションをつけたい時
  // カラーと拡大率の値を変更
  @include hover(('scale', 'color'), (
    scale: (
      child: '.child',
      value: 2
    ),
    color: (
      child: '.child',
      value: green
    ),
  ));
  // 注意!同じ要素に複数のアニメーションをつける場合、transitionプロパティがかぶるのでtransition mixinで個別指定が必要
  @include transition(('color', 'scale'));
}

コンパイル結果

dist.css
@media screen and (min-width: 768px) {
  .target-01 {
    -webkit-transition: color 0.4s ease-in-out;
    transition: color 0.4s ease-in-out;
  }
  .target-01:hover {
    -webkit-transition: color 0.4s ease-in-out;
    transition: color 0.4s ease-in-out;
  }
}
@media screen and (min-width: 768px) {
  .target-01:hover {
    color: #a00024;
  }
}

@media screen and (min-width: 768px) {
  .target-02 .child {
    -webkit-transition: color 0.4s ease-in-out;
    transition: color 0.4s ease-in-out;
  }
  .target-02 .child:hover {
    -webkit-transition: color 0.4s ease-in-out;
    transition: color 0.4s ease-in-out;
  }
}
@media screen and (min-width: 768px) {
  .target-02:hover .child {
    color: green;
  }
}

@media screen and (min-width: 768px) {
  .target-03 {
    -webkit-transition: -webkit-transform 0.6s ease-in-out;
    transition: -webkit-transform 0.6s ease-in-out;
    transition: transform 0.6s ease-in-out;
    transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;
  }
  .target-03:hover {
    -webkit-transition: -webkit-transform 0.3s ease-in-out;
    transition: -webkit-transform 0.3s ease-in-out;
    transition: transform 0.3s ease-in-out;
    transition: transform 0.3s ease-in-out, -webkit-transform 0.3s ease-in-out;
  }
}
@media screen and (min-width: 768px) {
  .target-03:hover {
    -webkit-transform: scale(1.2);
            transform: scale(1.2);
  }
}
@media screen and (min-width: 768px) {
  .target-03 {
    -webkit-transition: color 0.4s ease-in-out;
    transition: color 0.4s ease-in-out;
  }
  .target-03:hover {
    -webkit-transition: color 0.4s ease-in-out;
    transition: color 0.4s ease-in-out;
  }
}
@media screen and (min-width: 768px) {
  .target-03:hover {
    color: #a00024;
  }
}
@media screen and (min-width: 768px) {
  .target-03 {
    -webkit-transition: color 0.4s ease-in-out, -webkit-transform 0.6s ease-in-out;
    transition: color 0.4s ease-in-out, -webkit-transform 0.6s ease-in-out;
    transition: color 0.4s ease-in-out, transform 0.6s ease-in-out;
    transition: color 0.4s ease-in-out, transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;
  }
  .target-03:hover {
    -webkit-transition: color 0.4s ease-in-out, -webkit-transform 0.3s ease-in-out;
    transition: color 0.4s ease-in-out, -webkit-transform 0.3s ease-in-out;
    transition: color 0.4s ease-in-out, transform 0.3s ease-in-out;
    transition: color 0.4s ease-in-out, transform 0.3s ease-in-out, -webkit-transform 0.3s ease-in-out;
  }
}

@media screen and (min-width: 768px) {
  .target-04 .child {
    -webkit-transition: -webkit-transform 0.6s ease-in-out;
    transition: -webkit-transform 0.6s ease-in-out;
    transition: transform 0.6s ease-in-out;
    transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;
  }
  .target-04 .child:hover {
    -webkit-transition: -webkit-transform 0.3s ease-in-out;
    transition: -webkit-transform 0.3s ease-in-out;
    transition: transform 0.3s ease-in-out;
    transition: transform 0.3s ease-in-out, -webkit-transform 0.3s ease-in-out;
  }
}
@media screen and (min-width: 768px) {
  .target-04:hover .child {
    -webkit-transform: scale(2);
            transform: scale(2);
  }
}
@media screen and (min-width: 768px) {
  .target-04 .child {
    -webkit-transition: color 0.4s ease-in-out;
    transition: color 0.4s ease-in-out;
  }
  .target-04 .child:hover {
    -webkit-transition: color 0.4s ease-in-out;
    transition: color 0.4s ease-in-out;
  }
}
@media screen and (min-width: 768px) {
  .target-04:hover .child {
    color: green;
  }
}
@media screen and (min-width: 768px) {
  .target-04 {
    -webkit-transition: color 0.4s ease-in-out, -webkit-transform 0.6s ease-in-out;
    transition: color 0.4s ease-in-out, -webkit-transform 0.6s ease-in-out;
    transition: color 0.4s ease-in-out, transform 0.6s ease-in-out;
    transition: color 0.4s ease-in-out, transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;
  }
  .target-04:hover {
    -webkit-transition: color 0.4s ease-in-out, -webkit-transform 0.3s ease-in-out;
    transition: color 0.4s ease-in-out, -webkit-transform 0.3s ease-in-out;
    transition: color 0.4s ease-in-out, transform 0.3s ease-in-out;
    transition: color 0.4s ease-in-out, transform 0.3s ease-in-out, -webkit-transform 0.3s ease-in-out;
  }
}

ポイント

  • hover-〇〇 mixinでホバーアニメーションを定義。
  • hover mixinで引数をもとに指定されたアニメーションのmixinのみを呼び出すように分岐。
  • transition mixinで引数をもとに指定されたアニメーションのtransitionを設定。複数ある場合はtransitionプロパティの値をカンマ区切りで設定。
このスクラップは2023/02/22にクローズされました