【小数点実装】「(HTML +) CSS」のみを使って、今「もっとリアルな電卓」を作ってみた

# 完成品

まずは、CodePenで完成品を紹介します。ボタンを押して計算を試してみてください。
※ スマホだとロードに時間がかかる場合がありますので、表示されるまで少々お待ちください。

<p class="codepen" data-height="432" data-theme-id="light" data-default-tab="result" data-user="j5c8k6m8" data-slug-hash="WNwraqB" style="height: 432; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_calc2_last">
  <span>See the Pen <a href="https://codepen.io/j5c8k6m8/pen/WNwraqB">
  qiita_calc2_last</a> by j5c8k6m8 (<a href="https://codepen.io/j5c8k6m8">@j5c8k6m8</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

※ ページサイズ削減のため、 上記含めて本記事のCodePenの埋め込みコードは、
　 **桁数4、入力数3** のコードとなります。

**桁数8、入力数5のサンプル(完成品)は、 [こちらの Github Pages](https://j5c8k6m8.github.io/css_real_calculator/index.html)** から動作確認できます。
(12M程度のHTMLページとなります)

# はじめに

前回記事 **[「(HTML) + CSS」のみを使って、今「リアルな電卓」を作ってみた](https://qiita.com/j5c8k6m8/items/4695ee12b35e9c14047e)** について、
~~景品目当てにも関わらず、~~ 多くの反響をいただきました。ありがとうございます :laughing:
そして、 **[パソナテック賞 🥇準優秀賞](https://blog.qiita.com/summerfestival-2020-presents-winners/#%E6%BA%96%E5%84%AA%E7%A7%80%E8%B3%9E)** もいただくこともできました。
本企画の関係者様、記事を見ていただいた皆様、本当にありがとうございます:bow:

そんな中、 **[はてなブックマークのコメント](https://b.hatena.ne.jp/entry/s/qiita.com/j5c8k6m8/items/4695ee12b35e9c14047e)** に気になる要望が。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352865/7203de1f-b82d-2da4-bf03-88c0a08cb480.png)

**よろしくされました！**

# 課題

前回の記事は、 **CSSカウンターを使えば、電卓(加算/減算)を実現できそうだ** という前提があったため、
実現までのステップを細かく分けて、 ~~ゴリ押しで~~ 実装していくことで実現ができました。

しかし、今回の要望である小数点の実装では、
繰り上げは行わない仕様 (「0.5 + 0.5」は「1.0」として表示) として、
**小数点に応じて桁を合わせて整数として加算/減算し、表示上ピリオドを入れる**
という方針を考えました。

しかし、桁合わせの考慮のみだと、
**<font color="red">-0.9 など、1未満の数字 (特に負数) の表示がむつかしい</font>**
という課題があります。

具体的には、整数を、ピリオドを挿入することによって小数に見せる場合、

 - **1未満の正数の場合は、先頭に 0 を付与して表示する**
 - **1未満の負数の場合は、先頭に -0 を付与して表示する**

という工夫が必要となります。

正数については、**裏に0を用意して、合計値は重ねて表示** することで解決し、
負数については、上記に加え、 **ハイフンのみが表示されるフォント** と
**ハイフンが表示されないフォント** を作成して利用することで解決できると考えました。

具体的には、表示したい数に対して、フォントを重ねることで、少数の表示を実現します。

![qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352865/7c58e2e4-5c6e-4f7b-3d0f-ce781574d98b.png)


# 解説

それでは、前回同様、実装ステップごとにポイントとCodePenによるデモを載せます。
前回記事で解説した内容には触れませんので、必要に応じて、**[前回記事を参照](https://qiita.com/j5c8k6m8/items/4695ee12b35e9c14047e)** してください。

1. **[演算部分 / 入力ボタン表示制御](#1-%E6%BC%94%E7%AE%97%E9%83%A8%E5%88%86--%E5%85%A5%E5%8A%9B%E3%83%9C%E3%82%BF%E3%83%B3%E8%A1%A8%E7%A4%BA%E5%88%B6%E5%BE%A1)**
2. **[もっとリアルなスタイルの作成](#2-%E3%82%82%E3%81%A3%E3%81%A8%E3%83%AA%E3%82%A2%E3%83%AB%E3%81%AA%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90)**
3. **[ハイフンが表示されないフォントの作成 / 利用](#3-%E3%83%8F%E3%82%A4%E3%83%95%E3%83%B3%E3%81%8C%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90--%E5%88%A9%E7%94%A8)**
4. **[演算、リアルなスタイル、フォント制御のマージ / 最終調整](#4-%E6%BC%94%E7%AE%97%E3%83%AA%E3%82%A2%E3%83%AB%E3%81%AA%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%E5%88%B6%E5%BE%A1%E3%81%AE%E3%83%9E%E3%83%BC%E3%82%B8--%E6%9C%80%E7%B5%82%E8%AA%BF%E6%95%B4)**

## 1. 演算部分 / 入力ボタン表示制御

前回の記事での演算部分の処理の考慮点は、

 - 入力中の数字の桁数に応じて、各桁のカウンターの倍率を変える
 - `-` の場合は、総計の`counter-increment` を負数に変える

という、2点のみを考慮すれば実現できました。

今回は、上記2点に加え、

 - 小数点ボタンの追加
 - 合計値の計算における、小数点に応じた桁合わせ

という処理が加わり、演算部分の定義量がかなり増えます。

CodePenでは、SCSSでは処理量が多いとエラーになり、
CSSでも1MByteのファイル上限が設けられています。

また、前回の記事のカウンターの定義数は、 **2 × (桁数 + 1) × 入力回数最大数** となり
**桁数 ⇒ 8** 、 **入力回数最大数 ⇒ 10** として、**720** 個のカウンターの定義していましたが、
それだけでも、モバイルでの表示/演算は、想定以上に時間がかかるものになってしまいました。

**小数点の演算のための桁合わせを単純に追加すると、CSSの定義だけで 数百K行 以上になる**ため、
**前回のノウハウを活用して、CSS定義量が減る改善を行う対策** に加えて、
**レンダリング最適化[^1]されるようにCSSセレクタを改善** も行います。

[^1]: (参考) https://qiita.com/Yametaro/items/5c2d0267156ecc8562e4

CSSセレクタについて、
前回では、CSSカウンターで数える要素は、入力値用と、合計用で分けてはいませんでした。

カウンターで数える要素を同一にすると、CSSの定義を上書きするときに、
両方合わせて上書きする必要があるため、今回は分離します。

桁合わせを考慮した合計値の計算を、CSSカウンターのみで行うと、
入力値の桁と、最大の桁の両方を考慮する必要があります。

そこで、合計値は常に、桁合わせ数分用意し、表示側で最大の桁の数を表示するようにします。

また、前回は、 **同一クラスの後続のinputタグ** を数えていましたが、
HTMLの量を減らす代わりに、CSSの量が、入力数分増えてしまっていたため、
今回は、CSSカウンター用の要素を別途使用し、 **[一般兄弟結合子](https://developer.mozilla.org/ja/docs/Web/CSS/General_sibling_combinator)** ではなく、
**[隣接兄弟結合子](https://developer.mozilla.org/ja/docs/Web/CSS/Adjacent_sibling_combinator)** も使用します。

また、桁合わせのため、`., +, -, =` のinputタグを、桁毎にこの順で前方に配置し、
倍率は、 隣接兄弟結合子 の距離で求めるようにします。

また、入力パターンとしては、小数値を扱う場合に桁あふれの考慮も必要となります。
例えば、 $100000 + 0.000001$ のような計算の場合、
$100000.000001$は、電卓上表示できないため、
切り捨てや切り上げが必要となります。

当初、このケースはエラーとして扱おうと考えていましたが、桁合わせ用の合計値に入れる時点で
四捨五入することで、自然な振る舞いができそうです。
ただし、 **桁が切り捨てられる場合は、各入力値毎に四捨五入するのは仕様** とします。
（例えば、入力桁数を4桁として、 $1000 + 0.4 + 0.4 + 0.4$ は、$1001$ ではなく、$1000$と表示する)

これらを考慮して、以前作成した電卓に、以下の考慮をすれば実装できそうです。

 - `・(小数点)`のinput/labelを、入力数/桁数毎に追加
 - `+, -, =` のinput/labelを、入力数毎ではなく、入力数/桁数毎に修正
 - CSSカウンター用の要素を、入力数毎と合計値用に分離
 - 合計値を、桁合わせ分用意
 - 入力値毎に、チェックされた`・(小数点)` と `+, -, =` 距離から倍率を決定
 - すべての`・(小数点)` と `+, -, =` 距離の最大に応じて、合計値の表示を制御
 - 各桁毎の合計値は、入力値毎に四捨五入する

また、CSSセレクタは、原則単一のセレクタを使うことを考えると、クラス名が長くなります。
minify等を行っても、クラス名は圧縮できないため、
以下のようにクラス名に使うアルファベットを設計し、クラス名を短縮します。
~~自分用設計メモ~~


|アルファベット|意味|
|:-:|:--|
|n|入力値の番号(NUM)/表示用(wb)はボタン|
|d|桁数の番号(DIGIT)|
|x|0-9の番号|
|y|5-9の番号(四捨五入用)|
|z|小数計算用倍率|
|l|label要素|
|k|labl以外の表示制御用要素|
|o| +, -, =|
|p| + |
|m| - |
|e| = |
|r| ・(ピリオド)|
|v|CSSカウンター結果表示用要素|
|c|CSSカウンター/クリアボタン要素|
|s|CSSカウンター用要素/表示用(ws)はフォントカラー|
|g|CSSカウンター用グループ要素/表示用(wg)はフォント|
|a|1桁目のピリオド/表示用(wa)は+のボタン|
|b| 0-9の番号, ., +, -, =のうち1桁目のピリオド以外/表示用(wb)は+以外のボタン|
|q|エラー表示用要素|
|h|.入力なし用ピリオドinput|
|w|レイアウト用|
|f|エフェクト用種別(表示用)|
|u|バックグラウンド用(表示用)|
|i|ボタン行番号(表示用)|
|j|ボタン列番号(表示用)|
|t|小ネタ用|

さて、~~雑な~~前置きが長くなりましたが、制御部分のみの動作確認用のサンプルは以下の通りです。

<p class="codepen" data-height="240" data-theme-id="light" data-default-tab="result" data-user="j5c8k6m8" data-slug-hash="ExPOwYv" style="height: 240px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_calc2_1">
  <span>See the Pen <a href="https://codepen.io/j5c8k6m8/pen/ExPOwYv">
  qiita_calc2_1</a> by j5c8k6m8 (<a href="https://codepen.io/j5c8k6m8">@j5c8k6m8</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>


## 2. もっとリアルなスタイルの作成

本題は、小数点実装ですが、見た目のリアルさに変化がなければ、
~~LGTMが付かない~~記事は読んでもらえなそうなので、スタイルも改善します。

まずは、前回のスタイルでは、以下の3点が気になりました。

 - borderの外側と内側の差が生じてしまい、角が丸っぽくて電卓っぽくない
 - ハイライト用の光の左下と、右上が直線
 - 正面に対して点光源などを考慮したスタイルがない

ということで、改善前を左、改善後を右とした効果のサンプルは以下の通りです。
~~実装をどう変えたかは、実際のSCSSを見てください。~~
~~サンプルの色が気持ち悪いのは気にしない~~

<p class="codepen" data-height="400" data-theme-id="light" data-default-tab="result" data-user="j5c8k6m8" data-slug-hash="ExPzNEj" style="height: 400px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_calc2_part1">
  <span>See the Pen <a href="https://codepen.io/j5c8k6m8/pen/ExPzNEj">
  qiita_calc2_part1</a> by j5c8k6m8 (<a href="https://codepen.io/j5c8k6m8">@j5c8k6m8</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

続いて、今回は、transformを使い、3次元の回転の効果を使用して、よりリアルにすることを試みます。

詳しくは、省略しますが、**[rotate3d() -MND](https://developer.mozilla.org/ja/docs/Web/CSS/transform-function/rotate3d)** を使って頑張って調整します。
効果のサンプルは以下の通りです。

<p class="codepen" data-height="360" data-theme-id="light" data-default-tab="result" data-user="j5c8k6m8" data-slug-hash="MWKNOYL" style="height: 360px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_calc2_part2">
  <span>See the Pen <a href="https://codepen.io/j5c8k6m8/pen/MWKNOYL">
  qiita_calc2_part2</a> by j5c8k6m8 (<a href="https://codepen.io/j5c8k6m8">@j5c8k6m8</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

方針が決まったため、まずは、shadow等の効果を用いない、単色で電卓を作ります。
上記効果に加えて、以下の3点でより、リアルに見せるようにします。
 - 背景色は、液晶だけでなく、本体部分も微妙なグラデーションの繰り返しを入れる
 - ボタンが押せるような印象を持たせるため、ボタンの淵を加える
 - 色や配色の多い電卓とする

<p class="codepen" data-height="432" data-theme-id="light" data-default-tab="result" data-user="j5c8k6m8" data-slug-hash="MWygXjv" style="height: 432px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_calc2_2">
  <span>See the Pen <a href="https://codepen.io/j5c8k6m8/pen/MWygXjv">
  qiita_calc2_2</a> by j5c8k6m8 (<a href="https://codepen.io/j5c8k6m8">@j5c8k6m8</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

さて、これにshadow等の効果を加えて、ボタンを押したときのアニメーションも追加したのが以下です。

<p class="codepen" data-height="432" data-theme-id="light" data-default-tab="result" data-user="j5c8k6m8" data-slug-hash="poyoozL" style="height: 432px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_calc2_3">
  <span>See the Pen <a href="https://codepen.io/j5c8k6m8/pen/poyoozL">
  qiita_calc2_3</a> by j5c8k6m8 (<a href="https://codepen.io/j5c8k6m8">@j5c8k6m8</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

アニメーションは、前回と違い文字部分のZ軸方向の移動も感じられるため、
少し強調しすぎな感じがありますが、リアルさを感じられると思います。

上記CSSカウンター無し版では、アニメーションもスムーズに動くと思うので、
是非、ボタンを押してみてください。
※ スマホだとアニメーションが崩れる場合があります。

## 3. ハイフンが表示されないフォントの作成 / 利用

さて、課題で上げた通り、今回はハイフンが表示されないフォントを作成する必要があります。

[けしかん様の 「DSEG」フォント](https://www.keshikan.net/fonts.html) では、ライセンス上改変したフォントの利用は問題なさそうなので、
[GITHUBのリポジトリ](https://github.com/keshikan/DSEG) をForkしてフォントを作成します。

DSEGでは、fontforgeを利用してsfdファイルを作成しているようです。
sfdファイルは、テキストファイルなので、エディタで下記の編集を行います。

 - 絶対値用フォント: 「-」を空白を表す「!」と同じ内容に変更
 - 入力数背景用フォント： 上記に加えて、数字を「8」と同じ内容に変更
 - 符号のみフォント： 数字を空白を表す「!」と同じにしたのち、「.」と同じように、WIDTH, VWIDTHに0を設定

使わないsfdファイルを削除し、
LINUXで、前提である **Fontforge** と **Google woff2** をインストールし、
buildコマンドの内容を書き換え、`make`を実行すると、fontsフォルダ下に
woff2ファイルが作成できます。

これを、(ブランチを作成し)githubにpushし、tagも切っておきます。

これにより、以下のGITHUBのURLから、フォントファイルが下せるようになります。

https://github.com/j5c8k6m8/DSEG/raw/v0.0.2/fonts/DSEG7ClassicMini-BoldItalic-hidden-hyphen-all-eight.woff2

しかし、**これを直接CodePenで利用すると、以下のように CORS policy の制限に引っかかってしまいます。**

```
Access to font at 'https://github.com/j5c8k6m8/DSEG/raw/v0.0.2/fonts/DSEG7ClassicMini-BoldItalic-hidden-hyphen-all-eight.woff2' from origin 'https://cdpn.io' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'https://render.githubusercontent.com' that is not equal to the supplied origin.
https://github.com/j5c8k6m8/DSEG/raw/v0.0.2/fonts/DSEG7ClassicMini-BoldItalic-hidden-hyphen-all-eight.woff2 net::ERR_FAILED
```

そこでCDNの利用を考えます。

無料で利用できるCDNの [jsdelivr](https://www.jsdelivr.com/?docs=gh) では
**npmに登録しなくても、GITHUBのコードを以下のようなURL形式で利用** できます。

`https://cdn.jsdelivr.net/gh/user/repo@version/file`

先ほどのGITHUBのURLを、jsdelivrの形式のURLにしてみましょう。

https://cdn.jsdelivr.net/gh/j5c8k6m8/DSEG@v0.0.1/fonts/DSEG7ClassicMini-BoldItalic-hidden-hyphen-all-eight.woff2

無事、ダウンロードもでき、CodePenでも問題なく利用できました。


## 4. 演算、リアルなスタイル、フォント制御のマージ / 最終調整

上記の演算、スタイル、フォントをマージし、小ネタを仕込み、最終調整をしたものが以下となります。

<p class="codepen" data-height="432" data-theme-id="light" data-default-tab="result" data-user="j5c8k6m8" data-slug-hash="qBZbqGP" style="height: 432px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="qiita_calc2_4">
  <span>See the Pen <a href="https://codepen.io/j5c8k6m8/pen/qBZbqGP">
  qiita_calc2_4</a> by j5c8k6m8 (<a href="https://codepen.io/j5c8k6m8">@j5c8k6m8</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

さて、最終的にはCodePenでコンパイル済みのHTML/CSSとして動作させたいのですが、
CodePenでは、ファイルサイズが1Mの上限があるため、
ファイルサイズの大きくなるSCSSについて、minifyを含めてサイズを確認します。

|対象ファイル|桁数|入力数|ファイルサイズ<br/>圧縮無し|ファイルサイズ<br/>minify|
|:--|:-:|:-:|:-:|:-:|
|SCSS|-|-|29.8K|-|
|CSS|4|3|388.2K|322.7K|
|CSS|4|4|532.7K|443.0K|
|CSS|4|5|677.2K|563.4K|
|CSS|4|6|821.7K|683.8K|
|CSS|5|3|894.6K|737.3K|
|CSS|5|4|<font color="red">1239.4K</font>|<font color="red">1021.9K</font>|
|CSS|5|5|<font color="red">1584.2K</font>|<font color="red">1306.5K</font>|
|CSS|5|6|<font color="red">1929.0K</font>|<font color="red">1591.2K</font>|
|CSS|6|3|<font color="red">1840.1K</font>|<font color="red">1504.2K</font>|
|CSS|6|4|<font color="red">2559.8K</font>|<font color="red">2093.3K</font>|
|CSS|6|5|<font color="red">3279.4K</font>|<font color="red">2682.5K</font>|
|CSS|6|6|<font color="red">3999.1K</font>|<font color="red">3271.6K</font>|

最初は、8桁のサンプルをCodePenで記載する方針で本記事を執筆し始めました。
しかし、SCSSを短縮する余地は残っていますが、8桁のサンプルを実現するためには、
1 / 10 以下まで、減らす必要がありそうで、現実的ではありません。
ここはCodePenを潔くあきらめ、8桁のサンプルはGITHUBページで公開の方針とします。

リポジトリを新規作成し、node-sass、pug、pug-cliをインストールしたnode.jsのコンテナを作り、
pugとscssをコミットしたのち、単一HTMLへのビルド手順をREADME.mdに記載します。
~~ここまでCodePenのみで作ってきたし、npm-scriptsなんて使わない~~

対象のGITHUBリポジトリは **[j5c8k6m8/css_real_calculator - github](https://github.com/j5c8k6m8/css_real_calculator)** になります。
settingsから、GITHUB Pagesをmasterブランチに設定することで、
**[桁数8、入力数5の電卓](https://j5c8k6m8.github.io/css_real_calculator/index.html)** のページの作成ができました。

~~私のPCでEdgeで動作させた場合、ボタンを押したのち数秒フリーズします。
今後のブラウザの性能向上へ期待~~


# おわりに

~~JavaScriptに親をkillされたと思い込むことで、~~
なんとか、要望を実現することができました:tired_face: