pug/scssで20×20のピクロス(カービィ)を作成

2021/11/15に公開

こんにちはうえむーです。

今回はpug/scssで20×20のフィールドのピクロス(カービィ)を作成してpugとhtml、scssとcssのコード量を比較しました。
コード量をお見せする前に、pug/scssとは何かを説明したいと思います。

pug / scss とは

pug

HTMLを効率よくコーディングする為のテンプレートエンジンです。SCSSと似てコンパイルが必要になります。
共通パーツ(ヘッダー・フッター)付きの3ページ以上をコーディングをする時とか、同じコンポーネントを2桁以上コーディングしなければいけない時に適した言語です。

scss

CSSを効率良くスタイル調整する為のRubyで作られたスタイルシート言語です。Pugと同じくnode.js・gulpをインストールしてコンパイルする必要があります。
php・jsのように配列・変数などを用いることができ、(.section-00X)のようなセレクタや(color: whiteなど)よく利用する設定値などにも変数・配列を適用して効率よくスタイルシートを記述するのに適しています。

次に、pug / scss のメリット・デメリットについてお話します。

pug / scss のメリット・デメリット

メリット

・コード量が押さえられる
・変数が使える
・効率よくかける
・pugだと閉じタグいらない
・ファイルを分割管理できる

デメリット

・node.jsなどのインストールまたは、vscodeなど拡張機能を利用するなど環境が必要

メリットは結構たくさんあってどれも同意が得られるものが多いです、デメリットはnode.js/gulpをインストールまたはvscodeなど拡張機能を利用してその準備が整う必要があります。
初回は大変ですが、一度慣れてしまえば難しくないかと思います。

メリットとデメリットを説明した所で、pug/scssを利用できるように準備をしていきます。

node.js / gulp をインストール

まずは以下のページを開き、node.jsをダウンロードをしてインストールします。

https://nodejs.org/ja/

node.jsはjavaScriptエンジンで動くJavaScript環境です。
こちらをインストールするとgulp・sass・webpackなど様々なコンパイラーやビルドツールが利用できるようになります。
node.jsをインストールすると同時にnpmもインストールされます。

node.jsがインストールされたら

node -v

でコマンドを打ってインストールされているか確認しましょう。

v15.14.0

vXX.XX.X とコマンド結果が出たらOKです。

node.jsをインストールしたら自分が管理しやすいように MAMP・XAMPP・dockerなどの開発環境を用意して、分かりやすいフォルダ「例えば scss-pug」と作成

フォルダ直下に

npm init -y

のコマンド打ってpackage.jsonの初期化のファイルを生成していきます。

{
  "name": "scss-pug",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
}

ご今回は「gulp-sass」「gulp-pug」「gulp」を利用して、pug/scssをコンパイルしたいので以下のように入力してインストールします。

npm install gulp gulp-sass gulp-pug

インストール完了すると

package.lock ・ node_mocules

の[1]のようにフォルダーが生成されるようになり、[2]のようにpackage.jsonが以下のように更新されインストールされていることが確認出来ます。

[1]
scss-pug
├── node_modules
├── package-lock.json
└── package.json

[2] package.json

{
  "name": "scss-pug",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "gulp": "^4.0.2",
    "gulp-pug": "^5.0.0",
    "gulp-sass": "^5.0.0"
  }
}

次に、pug/scssをコンパイルできるように、gulpfile.jsをファイルを用意します。

scss-pug配下に空のgulpfile.jsを格納して以下のように記述して保存します。

const { src, dest, watch } = require("gulp");
const sass = require("gulp-sass");
const pug = require("gulp-pug");

// Sassをコンパイルする
const compileSass = () =>
   src("css/*.scss")
   .pipe(
       sass({
           outputStyle: "expanded"
       })
   )
   .pipe(dest("dest/css"));

// Sassファイルを監視
const watchSassFiles = () => 
  watch("css/*.scss", compileSass);

// pugをコンパイルする
const compilePug = () =>
   src("*.pug")
   .pipe(
       pug({
           pretty: true
       })
   )
   .pipe(dest("dest"));

// pugファイルを監視
const watchPugFiles = () =>
   watch("*.pug", compilePug);

// npx gulpで実行される関数
exports.default = () =>
   watchSassFiles();
   watchPugFiles();

これで、gulpでコンパイルできる準備が整いました。
試しに、pugファイル・ scssファイルを生成・修正して「gulp」と入力コンパイルします。
すると以下のようなディレクトリになりました。

├── css
│   └── test.scss
├── dest
│   ├── css
│   │   └── test.css
│   └── test.html
├── node_modules
├── gulpfile.js
├── package-lock.json
├── package.json
└── test.pug

これで準備が整いscss・pugが編集できるようになります。

pug/scssで20×20のフィールドのピクロスを作成し html / css のコード量を比較検証

pug/scssが編集できる準備が整いましたので20×20のフィールドのピクロス(カービィ)を作成します。
jsは一切利用してません。label/checkboxの要素を利用して実装しました。

codepen

vercel
https://pug-scss-picross.vercel.app/dest/picross_2.html

github
https://github.com/uemura5683/pug-scss-picross

実際に問題を解いて絵が完成したものはこちらになります。
一番苦労したのは絵が完成した時の色付けアニメーションの実装でした。

css/sass・html/pug のコード量の比較

css/sass・html/pugのコード量を比較しました。

・css 4854行  / scss 181行
・html 1260行 / pug 213行

scssの場合は4670行(96.28%)押さえられ、
pugの場合は1043行(83.1%)押さえられることができました。

コード量を押さえられる関数・変数を4つ紹介していきます。

コード量を押さえられる関数・変数(その1)

コード量を押さえられる関数・変数の1つめを紹介していきます。
一つ目はpugのwhile文です。

連番付きの20×20のフィールドを作成するには1つのマスのコードを400以上のコピペを繰り返し、連番を一つづつ編集する必要があります。
htmlの場合はwhile文などの関数は存在しないので20×20のフィールドを作成するのには一苦労かかりますし抜け漏れが発生する可能性があります。
しかし、pugのwhile文を利用すれば簡単に実装できます。
これを利用したことで83.1%押さえられることができました。

htmlの場合

        <input type="checkbox" id="rogic-checkbox-1"/>
        <label for="rogic-checkbox-1"></label>
        <input type="checkbox" id="rogic-checkbox-2"/>
        <label for="rogic-checkbox-2"></label>
        <input type="checkbox" id="rogic-checkbox-3"/>
....
        <label for="rogic-checkbox-400"></label>
        <input type="checkbox" id="rogic-checkbox-400"/>

pugの場合

        - var n = 1;
        while n <= 400
          input( type="checkbox" id=`rogic-checkbox-${n}` )
          label( for=`rogic-checkbox-${n++}` )

コード量を押さえられる関数・変数(その2)

コード量を押さえられる関数・変数の2つめはsass for文です。
20×20のフィールドの5フィールド毎に枠色を黒色にする時にfor文を利用しました。
cssは538行のコード量でしたが、for文を利用することで18行ぐらいですみました。


#container .rogic-inner div[class*='rogic-'] {
    transition: all .2s ease;
    box-shadow: inset 1px 1px 0 0 #cccccc;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    align-content: flex-end;
}

#container .rogic-inner div.rogic-7 {
  box-shadow: inset 1px 0 0 0 #000000, inset 0 1px 0 0 #cccccc;
}

#container .rogic-inner div.rogic-12 {
  box-shadow: inset 1px 0 0 0 #000000, inset 0 1px 0 0 #cccccc;
}

・・・

#container .rogic-inner div.rogic-432 {
  box-shadow: inset 1px 0 0 0 #000000, inset 0 1px 0 0 #cccccc;
}

#container .rogic-inner div.rogic-437 {
  box-shadow: inset 1px 0 0 0 #000000, inset 0 1px 0 0 #cccccc;
}


$line-min: 1;
$line-max: 441;

@for $i from $line-min through $line-max {
    @if ($i >= 22 and $i <= 42) or ($i >= 127 and $i <= 147) or ($i >= 232 and $i <= 252) or ($i >= 337 and $i <= 357) {
        @if $i % 21 == 7 or $i % 21 == 12 or $i % 21 == 17 {
            div.rogic-#{$i} {
              box-shadow: inset 1px 0 0 0 $black, inset 0 1px 0 0 $black;
            }
          } @else {
            div.rogic-#{$i} {
              box-shadow: inset 0 1px 0 0 $black, inset 1px 0 0 0 $gray;
            }
        }
    } @else {
        @if $i % 21 == 7 or $i % 21 == 12 or $i % 21 == 17 {
            div.rogic-#{$i} {
              box-shadow: inset 1px 0 0 0 $black, inset 0 1px 0 0 $gray;
            }
        }
    }
}

コード量を押さえられる関数・変数(その3)

コード量を押さえられる関数・変数の3つめはsass each文です。
ヨコ列の数字の箇所をセンタリングする時に実装しております。
each関数を利用することで10行ぐらいですみました。

#container .rogic-inner div.rogic-22 {
  align-content: center;
  justify-content: flex-end;
  align-items: center;
}

#container .rogic-inner div.rogic-22 span {
  width: auto;
}
...

#container .rogic-inner div.rogic-400 span {
  width: auto;
}

#container .rogic-inner div.rogic-421 {
  align-content: center;
  justify-content: flex-end;
  align-items: center;
}
$row: 22, 43, 64, 85, 106, 127, 148, 169, 190, 211, 232, 253, 274, 295, 316, 337, 358, 379, 400, 421;

@each $rows in $row {
    div.rogic-#{$rows} {
      align-content: center;
      justify-content: flex-end;
      align-items: center;
        span {
          width: auto;
        }
    }
}

コード量を押さえられる関数・変数(その4)

コード量を押さえられる関数・変数の4つめはsass マップ(連想配列)です。
連想配列の定義は以下のようにキーと値をセットしカンマで区切ってカッコ「()」 で括ります。
絵が完成した時の色付けアニメーションの実装する時に利用しました。
cssの場合は2015行のコード量だったのに対し、map関数を利用することで16行ぐらいですみました。

$変数名: (key1: value1, key2: value2, key3: value3...);

/* foundation */
$white:       #ffffff;
$black:       #000000;
$gray:        #cccccc;
。。。

/* background-color */
$row_white :115, 119;
$row_black :94, 98, 136, 140, 157, 161;
$row_darkgray :29, 30, 31, 。。。

/* map */
$bg_color: ($white: $row_white, $black: $row_black, $darkgray: $row_darkgray。。。 );

  input[id="rogic-checkbox-1"]:not(:checked) ~ input[id="rogic-checkbox-2"]:not(:checked) ~ input[id="rogic-checkbox-3"]:not(:checked) ~ input[id="rogic-checkbox-4"]:not(:checked) ~ 
....
  id="rogic-checkbox-399"]:checked ~ input[id="rogic-checkbox-400"]:not(:checked) ~
.rogic-inner {
    transition: all .2s ease;
    box-shadow: inset 0 0 5px 2px $gray;
      div[class*='rogic-'] {
        transition: all .2s ease;
        z-index: 1;
      }
      @each $background, $classname in $bg_color {
        @each $rows in $classname {
          div.rogic-#{$rows} {
            background-color: $background;
          }
        }
      }
  }
}

絵が完成した時の動画はこちらになります。
https://twitter.com/uemuragame5683/status/1418730146144657409

まとめ・注意点

これを実装してみてコード量の比較もできたし、結構楽しかったです。
grid数が多い時は先ほど紹介した「while文・for文・each文・map関数」を利用すればコード量はすごく押さえられ実装は楽になりました。
grid数が多ければ多いほど、コンパイルした時のcssのコード量が多くなり、ファイルの容量が重くなりサイトが遅延しています。
これは個人の趣味でやっておりますが、業務で実装する場合は表示速度のことは考えた方がいいかもしれません。

Discussion