CSS電卓特設サイト

CSS電卓とは

Javascriptを一切使用せず、HTMLとCSSだけで構築された電卓です。

通常、電卓のような「計算処理」や「状態管理」はJavaScriptなどのスクリプト言語が必須ですが、本作品ではCSSのみで四則演算を実現しています。

CSSって何?という方向けの、解説ページは.....

すみません、ありません。

(本サイトは“CSSの限界”をテーマにしているため、基礎説明は割愛しています)

本当に計算できるの?

まずは実物をお見せします。

実際に計算してみてください。

3桁の数同士の四則演算はできましたか?

例) 308 × 912 = 280896

※挙動はブラウザによって変わりますが、Chromium系でバグが最小になります。

目的

CSSの限界を知りたい

「制約下でのUI/UX設計」「ボタンの押下状態の管理をCSSで行う」これらをテーマに挑戦しました。

概要

CSS仕様書を読み漁っていたら閃きました。

:has()など最新のCSSを追いながら、何度もリファクタリングを繰り返して現在の形に至ります。

ブラウザによる挙動の違いや小数計算のcalc()の丸め誤差に悩まされ、CSS変数の組み合わせ方を何度も調整しました。

どうやって作った?

後ほど詳細な試行錯誤の過程を記述しますが、ここでは手短にまとめます。

忙しい人以外はこのセクションをパスしてください

(まだ準備中なので...)

仕組み・コード解説

勿体ぶらずに、まずはコードを出します。

<form>

.

.

.

...出したいです

.

追記 )) 全部コードを書くには余白が狭すぎました...

*コードはこちらで公開しています。

ここからは前述(?)のコードを4つに分けて解説していきます。

a.入力

ラジオボタンを使って入力状態を管理します。

以下のラジオボタンを触ってみてください。入力結果が変化します。

*各ボタンが数字などに対応しています。

X

演算子

Y

入力結果:

<input type="radio" name="x"> <input type="radio" name="x"> <input type="radio" name="x"> <div class="test0-x"></div>

/* 一つ目のラジオボタンがチェックされた時、1を表示 */ :has([name="x"]:nth-child(1):checked) .test0-x::before{ content: "1"; } /* 以下同様 */ :has([name="x"]:nth-child(2):checked) .test0-x::before{ content: "2"; } :has([name="x"]:nth-child(3):checked) .test0-x::before{ content: "3"; }

この電卓ではこのラジオボタンを百個以上使うことによって、8桁×8桁の10240000000000000000通りの計算を表現できます。

b.演算

擬似クラス「:checked」で入力状態を判定

擬似クラス「:has()」とも併せて使います

入力状態に応じたCSS変数を:rootに指定

/* 百の位を3に設定 */ :has([data-x0="3"]:checked) { --x0: 3; }

計算はcalc()で

以下は、CSS変数Xに3桁の数を設定するCSSです。

/* 百の位を100倍、十の位を10倍にして加算 */ :root{ --X: calc(var(--x0)*100 + var(--x1)*10 + var(--x2)) }

c.出力

電卓の上部には計算結果を表示するエリアがあります。

CSS変数による演算結果を文字に起こす過程にも苦労しました。

計算結果ごとに表示を指定する原始的なものから、CSSアニメーションによるもの、CSSカウンターによるものまで様々です。

以下は、CSSカウンターによる出力の最小構成。

/* 計算結果エリアにCSSカウンターを定義 */ #result{ counter-reset: n calc(var(--X) * var(--Y)); } /* CSSカウンターの値を表示 */ #result .digit-x01::before{ content: counter(n); }

counter-resetに演算結果をcalc()で設定。

複数桁の場合は、var(--x)の部分もcalc()で複雑に設定。

ゼロ徐算の表示にも注目してみてください。

d.UI

計算の精度だけでなく、UIもこだわり抜きました。

この電卓にはラジオボタンが百個以上使われていますが、

入力状態に応じてボタンの表示状態を変更

したり、disabled化しています。

*コード単純化の観点から、ラジオボタンにラッパーをつけずに変形させてボタンにしています。

リセットボタンなど特殊ボタンの実装

、そしてグリッドレイアウトを基盤としたデザインの進化にも注目してみてください。

電卓の歴史

初代電卓

かけ算のみ計算できます。

計算結果を「要素の幅」に設定、@containerで参照

ダミー要素の幅をcss変数のcalc()の形で指定し、その幅を@containerで取得し、条件分岐して計算結果を表示しています。

3×4でも、6×2でも同じ分岐にたどり着きます。

リセットボタンは初代から実装していました。

<input type="reset">

この電卓は、筆者史上初のCSS電卓であるため、当時としては画期的でした。

しかし、この電卓の最大の欠点として、計算結果の数に比例してコード量が増えてしまう点が挙げられます。

2代目電卓

CSSアニメーションによる結果表示

計算結果の擬似要素(::before)のcontentプロパティをCSSアニメーションで切り替え、そのanimation-iteration-countをcss変数で設定しています。

/* 割り算の場合。1の位を表示するコード */ #result::before{ animation-name: result; animation-duration: 5ms; animation-fill-mode: both; animation-timing-function: step-end; animation-iteration-count: calc(var(--X) / var(--Y) / 10); } @keyframes result{ 0%{ content: "0"; } 10%{ content: "1"; } 20%{ content: "2"; } 30%{ content: "3"; } 40%{ content: "4"; } 50%{ content: "5"; } 60%{ content: "6"; } 70%{ content: "7"; } 80%{ content: "8"; } 90%{ content: "9"; } 100% {content: "0"; } }

アニメーション?どういうこと?という方は、試しにゼロ徐算してみてください。

*ゼロ徐算すると、多くのブラウザではanimation-iteration-countの値が無限大(infinite)に発散するためアニメーションが無限に続きます。

animation-iteration-countは小数値を取りうるため、例えばXに6、Yに3を代入した場合は「0.2」の値が設定され、contentが"2"の状態で停止するわけです。

・・・このように、計算パターンに比例してコード量が増える問題を華麗に解決したのですが...

計算結果が1未満の場合は未対応

Chromium以外では丸め誤差が発生

計算結果が小数や負の数になる場合に対応したい!

1桁同士の計算じゃ物足りない!

3代目電卓

ついに複数桁対応

ついにマイナス対応

ブラウザ間の挙動の差を是正

UI大幅変更

CSSカウンターによる結果表示に切り替えました。

*CSSアニメーションの方法と比較して、Safariでの誤差をほぼ無くせます。Firefoxでも誤差を最小限に抑えました。

計算結果が負の数になる場合を対応しました。

UIの改良を行い、disabled化を進めました。

計算結果が小数値を取りうる割り算のみ、2代目電卓のアニメーションによる出力を採用しました。

割り算の計算結果が気持ち悪い

試しに「3÷9」を計算すると、「00000000.333333」と出力され、頭に0が付いてしまいます。

それに、拡張性も低い。

符号変更、浮動小数点、1文字消去などの機能を実装するには、コードの再構築が必要でした。

4代目電卓へ

UIを一新し、より電卓らしくなりました。

46000以下の数同士なら、全ブラウザで誤差なし

符号を変更できるようになりました。

割り算は整数部分出力のみcssカウンターを使用することにより、頭に0がつく問題を解消。

割り切れない場合に「...」をつけました。

1文字だけ消す機能⌫が欲しい

小数同士の計算ができるようにしたい

全体を名前空間化して扱いやすくしたい

*「:has()」が頭につくセレクターは、読み込み速度がページサイズに依存するため、@scopeで囲むことで解消を図りたい。

番外編:最小構成の電卓

1
2
3
4
5
6
7
8
合計は:

<div class="calc-wrap"> <div style="--n:1"> <input type="checkbox">1 </div> <div style="--n:2"> <input type="checkbox">2 </div> <div style="--n:3"> <input type="checkbox">3 </div> <div style="--n:4"> <input type="checkbox">4 </div> <div style="--n:5"> <input type="checkbox">5 </div> <div style="--n:6"> <input type="checkbox">6 </div> <div style="--n:7"> <input type="checkbox">7 </div> <div style="--n:8"> <input type="checkbox">8 </div> <div class="result">合計は:</div> </div>

.calc-wrap :has(:checked) { counter-increment: n var(--n); } .calc-wrap .result::after { content: counter(n); }

コードはフリーです。自由にご参照ください。

最新の電卓を特別に公開!

小数同士の計算ができます。

一文字を消す機能「⌫」も実装しました!

CSSを@scopeで囲み、扱いやすくなりました

*5÷(-8)など、計算結果が-1以上0未満になる計算で誤った結果が出力される場合があります。符号(+/-)を変更した場合は特に。

*計算結果の上限は±2147683647です。

さて、ボタンが1つだけ余りましたね ( 「?」のボタン、拡張用 )。

良い使い道が見つかりましたら今後実装していこうと思いますので今後もよろしくお願いします!!

関連作品

CSSの限界を試す作品は他にもあります!

診断サイト、乱数発生器、SPA、まるばつゲームほか...近日公開予定

今後の当サイトの発展にご期待ください。

まるばつゲーム

SPA