はじめに
みんなご存知FizzBuzz (Wikipedia)を、CSSメタ言語が普及した現在だとどうなるのか今更やってみた。
条件
1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。
[どうしてプログラマに・・・プログラムが書けないのか?]
(http://www.aoky.net/articles/jeff_atwood/why_cant_programmers_program.htm) - Jeff Atwood / 青木靖 訳
CSSはプログラムじゃないので直接HTMLを出力できないため、特別条件をプラスする。
・ HTMLは先に与えられてる
・ SEO、セマンティクス、アクセシリビティなどは無視する
HTMLは以下とする。
ul>li{i}*100 <!-- 数字付き -->
ul>li*100 <!-- 数字なし -->
CSSでFizzBuzz
CSSで行う場合、
- 擬似クラス:nth-child(:nth-of-type)を使う
-
先に表示されてる数字をどのように消すか
がポイントになる。
font-sizeでいらない数字を消す
ul>li{i}*100
// 3の倍数と5の倍数のフォントサイズを0にして見えなくする。
li:nth-child(3n),
li:nth-child(5n){ font-size: 0; }
// 擬似要素に文字を入れる。フォントサイズを戻せば見えるようになる。
// ::beforeと::afterに分けることで、公倍数のときは両方が表示される。
li:nth-child(3n)::before {content: "Fizz"; font-size: 1rem;}
li:nth-child(5n)::after {content: "Buzz"; font-size: 1rem;}
counter-incrementで数字も出力する
数字を出す方法を調べてるとCSSカウンタ **counter-increment**というものを見つけた。
使い道はまだよくわからない…。
ul>li*100
// 1)::beforeにカウンタセット
ul { counter-reset: num;} // カウンタリセット
li { counter-increment: num;} // numカウンタを増やしていく
li::before { content:counter(num) ""; }// li::beforeにカウンタ数表示
// 3) 5の倍数の::beforeが残るので空contentにして消す。
// "Fizz"を出力する前に消さないと3*5の倍数の時に"Fizz"が消えるので注意。
li:nth-child(5n)::before { content: "";}
// 2) 3の倍数の::before、5の倍数::afterにそれぞれ"Fizz"、"Buzz"を入れる。
// 3の倍数の::beforeは"Fizz"に上書きされるのでOK。
li:nth-child(3n)::before { content: "Fizz";}
li:nth-child(5n)::after { content: "Buzz";}
記述順に依存してるのがとても怖い。
【未来】list-style-type: <string>が使える、ようになるかも
<string>
特定の文字列を、リストのマーカーとして使用します。
ul -> ol
にしてビュレットの数字を3n,5n,3*5nの時にstringにすればできそう。
残念ながらまだ草案で実装ブラウザないみたい?
SassでFizzBuzz
メタ言語になると制御構文(if,each,while…)が使えるのでやれることが広がる。
よくある余剰で解く方法
スタイルシート感はゼロ。
@for $i from 1 through 100 {
// 条件分岐で表示するものを出し分ける
@if $i % (3 * 5) == 0 {
li:nth-child(#{$i})::before {content: "FizzBuzz";}
}
@else if $i % 3 == 0 {
li:nth-child(#{$i})::before {content: "Fizz";}
}
@else if $i % 15== 0 {
li:nth-child(#{$i})::after {content: "Buzz";}
}
@else {
li:nth-child(#{$i})::before {content: "#{$i}";}
}
}
list(配列)使う方法
1〜15番目までの並びを繰り返していくので、配列にして取り出していく方法があると聞いた。
Sassでも配列と同じ機能(list,map)があるのでやってみた。
が、nth($list, $n)
が0だとerorrが返ってくる仕様との戦いになってつらい。
拡張したところでスタイルシートはプログラム言語じゃない現実がつらい。
// 3n,5n,3*5n番目には文字、それ以外は空欄のリストをセットする。
$list : "","","Fizz","","Buzz","Fizz","","","Fizz","Buzz","","Fizz","","","FizzBuzz";
@for $i from 1 through 100 {
// リストの長さ(15=3*5)
$length: length($list);
// contentに入れる値。先に書いておかないとスコープが働いてエラーになる★つらい★
$content: "";
// 余剰を使って割り当てる。
// でも割り切れる(== 0)とエラーになるからその時だけ別の処理をする★つらい★
// リストの長さ(3*5=15)で割り切れない時 → 配列の値をそのままいれる
@if $i % $length != 0 {
$content: nth($list, $i % $length);
}
// 割り切れる時 → $length(=15番目)の値を入れる
@else {
$content: nth($list,$length);
}
// $contentが空欄""じゃないときはそのままリストの文字を表示させる
@if $content != "" {
li:nth-child(#{$i})::before {content: "#{$content}";}
}
// $contentが空欄""の時は$iの数字を表示させる
@else {
li:nth-child(#{$i})::before {content: "#{$i}";}
}
}
割り切ってもnthが0にならないように1を足すとちょっとスッキリした。
@for $i from 0 through 100 {
$content: nth($list, $i % 15 + 1);
@if $content != "" {
li:nth-child(#{$i + 1})::before {content: "#{$content}";}
}
@else {
li:nth-child(#{$i + 1})::before {content: "#{$i + 1}";}
}
}
さいごに
あんまり使わない機能を知る良い勉強になった。
listを使う方法は、パターンが決まってる値を無限に繰り返したい時があれば使えるかも?。
mixinを駆使する方法もあるようなので随時試してみたい。