第5章 現場で使える実践Sassテクニック

5-2 レイアウト・パーツで使えるテクニック

clearfix をミックスインで活用する

@mixin clearfix {
    &::after {
        content: "";
        display: block;
        clear: both;
    }
}
@mixin clearfix {
    &::after {
        content: "";
        display: block;
        clear: both;
    }
}
.item {
    background: #eee;
}
.item::after {
    content: "";
    display: block;
    clear: both;
}
.item .image {
    float: left;
    width: 100px;
}
.item .text {
    float: left;
}

変数を使って、サイドバーの幅を自動的に計算する

// 全体の幅
$wrap-width: 960px;

// メインエリアの幅
$main-width: 640px;

// サイドバーの幅
$side_width: $wrap_width - $main_width - 20;


#contents {
    width: $wrap_width;
}
#main {
    width: $main_width;
}
#side {
    width: $side_width;
}
#contents {
  width: 960px;
}
#main {
  width: 640px;
}
#side {
  width: 300px;
}
#side {
  width: 294px;
}

nullで簡単に条件分岐をしてレイアウトをする

$height: false;
.item {
    width: 500px;
    @if $height {
        height: $height;
    }
}
.item {
  width: 500px;
}
// 高さが必要な場合は値を単位付きで。不要な場合はnull。
$height: null;
.item {
    width: 500px;
    height: $height;
}
.item {
  width: 500px;
}
@mixin itemBox ($width, $height, $margin:null, $padding:null) {
    width: $width;
    height: $height;
    margin: $margin;
    padding: $padding;
}

.itemA {
    // 不要なプロパティはnull。
    @include itemBox(100px, null, 10px, 20px);
}
.itemB {
    @include itemBox(null, auto, 20px auto);
}
.itemA {
  width: 100px;
  margin: 10px;
  padding: 20px;
}

.itemB {
  height: auto;
  margin: 20px auto;
}
$duration: null;
a {
    transition: all $duration linear;
}
a {
  transition: all linear;
}

calcとSassを組み合わせて四則演算を便利に使う

.item {
    width: calc(100% - 1px * 2);
}
.item {
  width: calc(100% - 1px * 2);
}
$border: 1px;
.item {
    width: calc(100% - $border * 2);
}
.item {
  width: calc(100% - $border * 2);
}
$border: 1px;
.item {
    width: calc(100% - #{$border} * 2);
}
.item {
  width: calc(100% - 1px * 2);
}
$border: 100px - 1px * 2;
.item {
    width: calc(#{$border});
}
.item {
  width: calc(98px);
}
$border: "100px - 1px * 2";
.item {
    width: calc(#{$border});
}
.item {
  width: calc(100px - 1px * 2);
}
$box: "100px - 1px * 2";
$contents: "100% - 20px";
.item {
    width: calc(#{$contents} - #{$box});
}
.item {
  width: calc(100% - 20px - 100px - 1px * 2);
}

@for を使って余白調整用のclassを生成する

$spaceClass: true !default;
$spacePadding: false !default;
$endValue: 10 !default;

@if $spaceClass {
    @for $i from 0 through $space_endValue {
        .mt#{$i * 5} {
            margin-top: 5px * $i !important;
        }
        .mb#{$i * 5} {
            margin-bottom: 5px * $i !important;
        }
        @if $spacePadding {
            .pt#{$i * 5} {
                padding-top: 5px * $i !important;
            }
            .pb#{$i * 5} {
                padding-bottom: 5px * $i !important;
            }
        }
    }
}
.mt0 {
  margin-top: 0px !important;
}
.mb0 {
  margin-bottom: 0px !important;
}
.mt5 {
  margin-top: 5px !important;
}
.mb5 {
  margin-bottom: 5px !important;
}

...(略)...

.mt50 {
  margin-top: 50px !important;
}
.mb50 {
  margin-bottom: 50px !important;
}

リストマーカー用の連番を使ったclass名を作成する

%markBase {
    padding-left: 15px;
    background-position: 0em .5em;
    background-repeat: no-repeat;
}

@for $i from 1 through 3 {
    .mark_#{$i} {
        @extend %markBase;
        background-image: url(../img/mark_#{$i}.png);
    }
}
.mark_1, .mark_2, .mark_3 {
  padding-left: 15px;
  background-position: 0em .5em;
  background-repeat: no-repeat;
}

.mark_1 {
  background-image: url(../img/mark_1.png);
}
.mark_2 {
  background-image: url(../img/mark_2.png);
}
.mark_3 {
  background-image: url(../img/mark_3.png);
}

連番を使ったclass名のゼロパディング(0埋め)をする

$tmp: "";
@for $i from 1 through 15 {
    @if $i < 10 {
        $tmp: "0#{$i}";
    } @else {
        $tmp: $i;
    }
    .mark_#{$tmp} {
        background-image: url(../img/mark_#{$tmp}.png);
    }
}
.mark_01 {
  background-image: url(../img/mark_01.png);
}

.mark_02 {
  background-image: url(../img/mark_02.png);
}

...(略)...

.mark_14 {
  background-image: url(../img/mark_14.png);
}

.mark_15 {
  background-image: url(../img/mark_15.png);
}

文字リンクカラーのミックスインを作る

@mixin link-color($normal, $hover) {
    color: $normal;
    &:hover { 
        color: $hover;
        text-decoration: none;
    }
}

a {
  @include link-color(#f00, #00f);
}
a {
  color: red;
}
a:hover {
  color: blue;
  text-decoration: none;  
}
$normal: #f00;
$hover: #00f;
@mixin link-color($n:$normal, $h:$hover) {
    color: $n;
    &:hover { 
        color: $h;
        text-decoration: none;
    }
}

a {
  @include link-color;
}
a {
  color: red;
}
a:hover {
  color: blue;
  text-decoration: none;
}
@mixin link-color2($n) {
    color: $n;
    &:hover { 
        color: lighten($n, 30%);
        text-decoration: none;
    }
}

a {
  @include link-color2(#f00);
}
a {
  color: red;
}
a:hover {
  color: #ff9999;
  text-decoration: none;
}

複数の値を@eachでループし、ページによって背景を変更する

$setBG: top, about, company, contact;

@each $i in $setBG {
    .body-#{$i} {
        background-image: url(../img/bg_#{$i}.png);
    }
}
.body-top {
  background-image: url(../img/bg_top.png);
}
.body-about {
  background-image: url(../img/bg_about.png);
}
.body-company {
  background-image: url(../img/bg_company.png);
}
.body-contact {
  background-image: url(../img/bg_contact.png);
}
$setBG: top red, about blue, company green, contact yellow;
@each $i in $setBG {
    .body-#{nth($i, 1)} {
        background-image: url(../img/bg_#{nth($i, 2)}.png);
    }
}
.body-top {
  background-image: url(../img/bg_red.png);
}

.body-about {
  background-image: url(../img/bg_blue.png);
}

.body-company {
  background-image: url(../img/bg_green.png);
}

.body-contact {
  background-image: url(../img/bg_yellow.png);
}
$setBG: (top red)(about blue)(company green)(contact yellow);
$setBG: (top,red)(about,blue)(company,green)(contact,yellow);
$setBG: ((top)(red))((about)(blue))((company)(green))((contact)(yellow));

シンプルなグラデーションのミックスインを作る

@mixin linear-gradient($color: #f00, $way:to bottom, $percent: 20%) {
    background-image: linear-gradient($way,$color 0%, lighten($color, $percent) 100%);
}
.item {
    @include linear-gradient;
}
.item {
    background-image: linear-gradient(to bottom, #f00 0%, #ff6666 100%);
}
.item {
    @include linear-gradient(#999, left, 50%);
}
.item {
    background-image: linear-gradient(to bottom, #f00 0%, #ff6666 100%);
}

Map型と@eachを使ってSNSアイコンを管理する

// Map型を使って定義
$sns-colors: (
    twitter: #1b95e0,
    facebook: #3b5998,
    googleplus: #dc4e41,
);

// SNSアイコン
.sns {
    &__btn {
        background-repeat: no-repeat;
        // @each で処理を繰り返す
        @each $key, $value in $sns-colors {
            &.-#{$key} {
                background-image: url(/img/icon_#{$name}.png);
                background-color: $value;
            }
        }
    }
}
.sns__btn {
    background-repeat: no-repeat;
}
.sns__btn.-twitter {
    background-image: url(/img/icon_twitter.png);
    background-color: #1b95e0;
}
.sns__btn.-facebook {
    background-image: url(/img/icon_facebook.png);
    background-color: #3b5998;
}
.sns__btn.-googleplus {
    background-image: url(/img/icon_googleplus.png);
    background-color: #dc4e41;
}
// Mapを使ってファイル名と背景色を定義
$sns-colors: (
    twitter: '01.png' '#1b95e0',
    facebook: '02.jpg' '#3b5998',
    googleplus: 'gplus.png' '#dc4e41',
);
// SNSアイコン
.sns {
    &__btn {
        background-repeat: no-repeat;
        // @each で処理を繰り返す
        @each $key, $values in $sns-colors {
            &.-#{$key} {
                background-image: url(/img/icon/#{nth($values, 1)});
                background-color: #{nth($value, 2)};
            }
        }
    }
}
.sns__btn {
  background-repeat: no-repeat;
}
.sns__btn.-twitter {
  background-image: url(/img/icon/01.png);
  background-color: #1b95e0;
}
.sns__btn.-facebook {
  background-image: url(/img/icon/02.jpg);
  background-color: #3b5998;
}
.sns__btn.-googleplus {
  background-image: url(/img/icon/gplus.png);
  background-color: #dc4e41;
}
// SNS設定
$sns-config:(
    colors: (
        twitter: #1b95e0,
        facebook: #3b5998,
        googleplus: #dc4e41,
    ),
    sizes: (
        sm: 25%,
        md: 50%,
        lg: 100%,
    )
);
// SNSアイコン
.sns {
    &__btn {
        @each $name, $color in map-get($sns-config, 'colors') {
            &.-#{$name} {
                background: $color url(/img/icon_#{$name}.png) no-repeat center;
            }
        }
        @each $size, $width in map-get($sns-config, 'sizes') {
            &.-#{$size} {
                width: $width;
            }
        }
    }
}
.sns__btn.-twitter {
    background: #1b95e0 url(/img/icon_twitter.png) no-repeat center;
}
.sns__btn.-facebook {
    background: #3b5998 url(/img/icon_facebook.png) no-repeat center;
}
.sns__btn.-googleplus {
    background: #dc4e41 url(/img/icon_googleplus.png) no-repeat center;
}
.sns__btn.-sm {
    width: 25%;
}
.sns__btn.-md {
    width: 50%;
}
.sns__btn.-lg {
    width: 100%;
}

値が比較しづらいz-indexをMap型で一括管理する

$layer: (
    modal : 100,
    header : 20,
    tooltip : 10,
    default : 1
);
@mixin z-index($key) {
    z-index: map-get($layer, $key);
}
.modal {
    @include z-index(modal);
}
.header {
    @include z-index(header);
}
.tooltip {
    @include z-index(tooltip);
}
.modal {
  z-index: 100;
}
.header {
  z-index: 20;
}
.tooltip {
  z-index: 10;
}

@functionを使ってpx指定する感覚でフォントサイズをrem指定する

$baseFontSize: 16;
html {
    font-size: $baseFontSize + px;
}
@function rem($pixels, $context: $baseFontSize) {
    @return $pixels / $context * 1rem;
}
.text {
    font-size: rem(12);
}
.text {
  font-size: 0.875rem;
}
.text {
    font-size: rem(14, 12);
}
.text {
  font-size: 1.16667rem;
}

ネストしたセレクタをselector-replace()を使って書き換える

.block {
    .item-A {
        a {
            background: red;
            @at-root #{selector-replace(&,".item-A", ".item-B")} {
                background: blue;
            }
        }
    }
}
.block .item-A a {
    background: red;
}
.block .item-B a {
    background: blue;
}
@mixin replace($original, $replacement) {
    @at-root {
        #{selector-replace(&, $original, $replacement)} {
            @content
        }
    }
}
.block {
    .item {
        a {
            width: 85%;
            @include replace(".block", ".block .inner") {
                width: 100%;
            }
        }
    }
}
.block .item a {
  width: 85%;
}
.block .inner .item a {
  width: 100%;
}
#main {
    .block {
        .link {
            padding-left: 20px;
            @include replace(".link", "a.link") {
                display: inline-block;
                padding-left: 15px;
            }
        }
    }
}
#main .block .link {
  padding-left: 20px;
}
#main .block a.link {
  display: inline-block;
  padding-left: 15px;
}

selector-extend() を使い、親セレクタだけ変更して同一スタイルを適用させる

.block {
    .item-A {
        padding: 10px;
        ul {
        width: 500px;
            li {
                margin-bottom: 10px;
                a {
                    background: blue;
                }
            }
        }
    }
    .item-B {
        padding: 20px;
        ul {
            li {
                a {
                    background: blue;
                }
            }
        }
    }
}
.block {
    .item-A {
        padding: 10px;
        ul {
            width: 500px;
            li {
                margin-bottom: 10px;
                a {
                    @at-root #{selector-extend(&, ".item-A", ".item-B")} {
                        background: blue;
                    }
                }
            }
        }
    }
    .item-B {
        padding: 20px;
    }
}
.block .item-A {
  padding: 10px;
}
.block .item-A ul {
  width: 500px;
}
.block .item-A ul li {
  margin-bottom: 10px;
}
.block .item-A ul li a {
  background: blue;
}
.block .item-B {
  padding: 20px;
}
.block .item-B ul li a {
  background: blue;
}
.block .item-A {
  padding: 10px;
}
.block .item-A ul {
  width: 500px;
}
.block .item-A ul li {
  margin-bottom: 10px;
}
.block .item-A ul li a, .block
.item-B ul li a {
  background: blue;
}
.block .item-B {
  padding: 20px;
}