[Sass] scssで扱う配列でのイテレーションを理解する ~ cssだけでチャートを描画するための前知識


2020/05/15

今回はSassでのデータを配列と取り扱う上で、前もって理解しておきたい文法のお話です。

Sassの配列はかなり扱いが厳密ではなく、かなり緩く簡単に使うことができます。

その決まりの緩さ故に、たまにルール無用の配列を使ってしまうと訳が分からないイテレーションになってしまうことがあります。

Sass特有の配列の使い方を理解して、scssで使えるイテレーションのパターンに関してここで復習しておこうというのが今回の記事の目的です。

最終的には、d3jsのようなjs系のライブラリでのスクリプトを介したチャートの描画や、svg要素をxmlでベタ書きで打ち出したグラフなどに頼らない、純粋なcssだけを用いたスタイリンでチャートを描く方法を考えてみたいと思います。


scssの配列の文法色々

公式のDocumentationにもあるように、ここでsassの配列と呼んでいるのは、正確にはLists(リスト)と呼ばれています。

Lists contain a sequence of other values.(リストは連続した値を含むもの)がそもそもの定義のようです。

公式にもあるように、一元配列に関しては、丸括弧
()で配列の境界を示し、コンマ文字かスペース文字で区切ったもので定義されているので、以下の2つがそもそものリスト(配列)です。

            
            $array: (hoge1, hoge2, hoge3); // コンマ切り
// OR
$array: (hoge1 hoge2 hoge3); // スペース切り
        
ちなみに、中身なしの0個の要素をもつ場合にもリストが使えます。

一元配列に限れば、釘括弧
[]でもリストが定義できて、

            
            $array: [hoge1, hoge2, hoge3]; // コンマ切り
// OR
$array: [hoge1 hoge2 hoge3]; // スペース切り
        
と出来ます。

むしろこっちの表現の方が、従来の多言語の
配列に寄せて来ているような文法であり、配列を利用する際の紛らわしさがないので、sassの配列と呼ぶにふさわしいと思います。

実は
()[]には力関係があります。

例えば、配列を先に丸括弧
(hoge1, hoge2, ...)で構成された配列の中身には、釘括弧配列[piyo1, piyo2,...]は配列ではないものと見做されます。

丸括弧の方が釘括弧より強いという微妙な優先順位があるようです。

紛らわしいのがここからで、リストは一元配列と取り扱える場合には、括弧を省略できる仕様になっているため、以下は配列として機能してしまいます。

            
            $array: hoge1, hoge2, hoge3;
// OR
$array: hoge1 hoge2 hoge3;
        
これはシンタックスシュガー(糖衣構文)であり、見栄えもスッキリするので、他のサイトでもこちらをsassの配列として紹介している場合が多いです。


Sassの配列の操作色々

配列の取得は組み込み関数のnthを使って要素を引出します。

イテレーションのインデックスは、1からカウント開始するようです。

            
            // nth($array, $index):
nth($array, 1); // hoge1
nth($array, 2); // hoge2
        
配列の値の長さの取得はlengthが使えます。

            
            // length($array)
length($array); // 3
        
Sassの配列の位置を取得はindexを使います。

            
            // index($array, $element)
index($array, hoge1); // 1
index($array, hoge3); // 3
        

sassの配列をどう使うべきか?

結論を言うと、私見ではありますが、多次元配列を扱いたい際には、配列を[]で包んで、セパレータは,に限定するルールを開発グループ内で統一しておけば良いんではないかと思います。

javascriptとも文法的な意味で整合性がとれるので、忘れにくいとも言えます。

            
            // 多次元配列
$itemss: [[black, lime, fuchsia, olive], [blue, aqua], [yellow, red]];

$i: 0; $j: 0;
@each $items in $itemss {
  @each $item in $items {
    .group#{$i}-element#{$j}-bg {
      background: #{$item};
    }
    $j: $j+1;
  }
  $i: $i+1;
  $j: 0;
}
        
コンパイル後の出力は、

            
            .group0-element0-bg {
  background: black;
}

.group0-element1-bg {
  background: lime;
}

.group0-element2-bg {
  background: fuchsia;
}

.group0-element3-bg {
  background: olive;
}

.group1-element0-bg {
  background: blue;
}

.group1-element1-bg {
  background: aqua;
}

.group2-element0-bg {
  background: yellow;
}

.group2-element1-bg {
  background: red;
}
        
となります。

せっかくなので、同じ結果を得られるような配列の構成パターンを作って実験してみます。


釘かっこ"()"を配列のバウンダリー識別文字に使うパターン

繰り返しになりますが、リストの境界を示す際に()で包んでも、上と同じコンパイル結果となります。

            
            $itemss: ((black, lime, fuchsia, olive), (blue, aqua), (yellow, red));

$i: 0; $j: 0;
@each $items in $itemss {
  @each $item in $items {
    .group#{$i}-element#{$j}-bg {
      background: #{$item};
    }
    $j: $j+1;
  }
  $i: $i+1;
  $j: 0;
}
        

丸括弧カッコ"()"を使わない配列を使うケース

下のようにスペースとコンマを使うことで、頑張れば2次元の配列を取り扱うこともできます。

            
            $itemss: black lime fuchsia olive, blue aqua, yellow red;

$i: 0; $j: 0;
@each $items in $itemss {
  @each $item in $items {
    .group#{$i}-element#{$j}-bg {
      background: #{$item};
    }
    $j: $j+1;
  }
  $i: $i+1;
  $j: 0;
}
        
行列までならシンタックスシュガーでいける感じです。


スペースとコンマを逆に取り扱うケース

ひとつ実験として、先ほどの上のコードの配列でコンマとスペースを入れ替えてたパターンを試してみましょう。

            
            $itemss: black, lime, fuchsia, olive blue, aqua yellow, red;

$i: 0;
$j: 0;
@each $items in $itemss {
  @each $item in $items {
    .group#{$i}-element#{$j}-bg {
      background: #{$item};
    }
    $j: $j+1;
  }
  $i: $i+1;
  $j: 0;
}
        
このコードをコンパイルすると以下のような結果になります。

            
            .group0-element0-bg {
  background: black;
}

.group1-element0-bg {
  background: lime;
}

.group2-element0-bg {
  background: fuchsia;
}

.group3-element0-bg {
  background: olive;
}

.group3-element1-bg {
  background: blue;
}

.group4-element0-bg {
  background: aqua;
}

.group4-element1-bg {
  background: yellow;
}

.group5-element0-bg {
  background: red;
}
        
となり、期待していた出力とは全く異なった結果になってしまいます。

セパレータ間でも、コンマとスペースでの力関係があり、シンタックスシュガーを使う限りは、コンマが優位になり配列境界を示す文字に役割が変わります。

対して、スペースはあくまでもセパレータ識別記号として認識されるようです。

配列の修正

ではどうしてもスペース文字で配列境界に扱い、配列の中身をコンマ切りにしたいときがあるかも知れません。

どうすれば良いかというと、上記のコードから配列の要素にあたるブロックに
()[]で囲うことで、スペース切りでも元の結果を得ます。

            
            $itemss: (black, lime, fuchsia, olive) (blue, aqua) (yellow, red);

$i: 0; $j: 0;
@each $items in $itemss {
  @each $item in $items {
    .group#{$i}-element#{$j}-bg {
      background: #{$item};
    }
    $j: $j+1;
  }
  $i: $i+1;
  $j: 0;
}
        

コンマを使わない配列

いっそのこと、コンマ切りを止めたい場合には、

            
            $itemss: (black lime fuchsia olive) (blue aqua) (yellow red);

$i: 0;
$j: 0;
@each $items in $itemss {
  @each $item in $items {
    .group#{$i}-element#{$j}-bg {
      background: #{$item};
    }
    $j: $j+1;
  }
  $i: $i+1;
  $j: 0;
}
        
ともできます。

これも期待したコンパイル結果を得ます。


混ぜるな危険ケース

ここまで上のコードサンプルで試したように、sassで配列を扱う場合には、以下の主に3つのパターンが推奨されます。

            
            1. 括弧で配列のバウンダリーを示し、コンマを要素セパレータとして使う
2. 括弧で配列のバウンダリーをを示し、スペースを要素セパレータとして使う
3. コンマで配列のバウンダリーをを示し、スペースを要素セパレータとして使う
        
詰まりは、括弧とコンマとスペースが同時に存在しているような配列でも、配列のシンタックスチェックもなくコンパイルが通ってしまうので、意図としない結果に陥る可能性があります。

例えば割と無謀とも思える以下の配列も、期待したコンパイル結果を出力します。

            
            $itemss: [black lime fuchsia olive], blue aqua, (yellow, red);

$i: 0;
$j: 0;
@each $items in $itemss {
  @each $item in $items {
    .group#{$i}-element#{$j}-bg {
      background: #{$item};
    }
    $j: $j+1;
  }
  $i: $i+1;
  $j: 0;
}
        
まだ要素数も次元数も少ないうちは辛うじて読めるものの、データが複雑になったり、複数の配列を使ったりするほどなにがなんだかわからないようなコードになる温床になります。

例えば更に混ぜてしまった、以下のコードは、果たして上と同じ結果になるでしょうか?

...答え合わせはお手元のコンパイラで是非試してみてください。

            
            $itemss: ((black, lime, fuchsia, olive) blue, aqua [yellow, red]);

$i: 0;
$j: 0;
@each $items in $itemss {
  @each $item in $items {
    .group#{$i}-element#{$j}-bg {
      background: #{$item};
    }
    $j: $j+1;
  }
  $i: $i+1;
  $j: 0;
}
        
sassの配列を取り扱うルールのだけは早いうちから気をつけておきたいものです。


@forで配列をイテレーションするパターン

ここからは配列を用いたイテレーションの話を少しだけします。

上記では全て
@eachによる配列イテレーションでしたが、@forで回すなら以下のコードのようになります。

            
            $itemss: [[black, lime, fuchsia, olive], [blue, aqua], [yellow, red]];

@for $i from 1 through length($itemss) {
    @for $j from 1 through length(nth($itemss, $i)) {
        .group#{$i - 1}-element#{$j - 1}-bg {
            background: #{nth(nth($itemss, $i), $j)};
        }
    }
}
        
コードの行数はスッキリしています。

イテレーションが1から始まるので、要素数のカウントには注意が必要です。


@whileで配列をイテレーションするパターン

最後に@whileによる配列イテレーションのコード例になります。

while文とはいっても、continuebreakといった制御構文はないので、@whileに続く箇所に停止条件を記述するだけです。

            
            $itemss: [[black, lime, fuchsia, olive], [blue, aqua], [yellow, red]];

$i: 1;
@while $i <= length($itemss) {
    $j: 1;
    @while $j <= length(nth($itemss, $i)) {
        .group#{$i - 1}-element#{$j - 1}-bg {
            background: #{nth(nth($itemss, $i), $j)};
        }
        $j: $j+1;
    }
    $i: $i+1;
}
        
この@whileを用いたイテレーションは、より複雑な内部処理を行う場合に向いてます。

ここでもイテレーションが1から始まるので、要素数のカウントや配列要素のアクセスには注意が必要です。


おまけ 〜 今回のスタイルの利用例

せっかくなので、今回のテクニックを使ってを簡単な要素に配列的に取り出したスタイルをhtml要素に適用させてみます。

scssファイルの中身はこちらです。

            
            .circle {
    display: inline-block;
    width: 20px;
    height: 20px;
    border-radius: 50%;
}

$itemss: [[black, lime, fuchsia, olive], [blue, aqua], [yellow, red]];

$i: 0; $j: 0;
@each $items in $itemss {
    @each $item in $items {
        .group#{$i} {
            &.element#{$j}-bg {
                background: #{$item};
            }
        }
        $j: $j+1;
    }
    $i: $i+1;
    $j: 0;
}
        
html要素は以下のようなものに適用させます。

            
            <div>
    <div class="circle group0 element0-bg"></div>
    <div class="circle group0 element1-bg"></div>
    <div class="circle group0 element2-bg"></div>
    <div class="circle group0 element3-bg"></div>
</div>
<div>
    <div class="circle group1 element0-bg"></div>
    <div class="circle group1 element1-bg"></div>
</div>
<div>
    <div class="circle group2 element0-bg"></div>
    <div class="circle group2 element1-bg"></div>
</div>
        
...何の捻りもなくてすみませんが、見た目は以下のようになります。


まとめ

以上で見てきたように、(): 丸カッコ > []: 釘カッコ > ,: カンマ > : スペースの順で、配列を構成すると見なされる優先順位が決まります。

Sassはcssと比べて可読性の高いコーディングができるので、他者が読んでも変わりやすい書き方を心掛けるとよいとおもいます。