さぁ!検索しよう!

HTML,CSS,JavaScriptによる簡易電卓の作り方です。

DEMO

HTMLで電卓の構造を定義

始めに以下のHTMLを書き、電卓の構造を定義します。

<div class="calculator">
    <div class="display__wrap">
        <aside class="display">
            <div id="displaySym" class="display__sym"></div>
            <div id="displayNum" class="display__num"></div>
        </aside>
    </div>
    <nav class="nav">
        <div class="nav__inner nav__inner--left">
            <div class="nav__erase">
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--erase" data-value="ce" href="#">CE</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--erase" data-value="ca" href="#">CA</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--erase" data-value="del" href="#">DEL</a></div>
            </div>
            <div class="nav__num">
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="7" href="#">7</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="8" href="#">8</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="9" href="#">9</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="4" href="#">4</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="5" href="#">5</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="6" href="#">6</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="1" href="#">1</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="2" href="#">2</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="3" href="#">3</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="0" href="#">0</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="00" href="#">00</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--num" data-value="." href="#">.</a></div>
            </div>
        </div>
        <div class="nav__inner nav__inner--right">
            <div class="nav__operator">
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--operator" data-value="/" href="#">÷</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--operator" data-value="*" href="#">×</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--operator" data-value="-" href="#">-</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--operator" data-value="+" href="#">+</a></div>
                <div class="nav__btn__wrap"><a class="nav__btn nav__btn--operator" data-value="=" href="#">=</a></div>
            </div>
        </div>
    </nav>
</div>

HTMLのポイントは以下の通りです。

ディスプレイを2つに分ける

出力するディスプレイを入力値用display__numと演算子用display__symの2つに分けます。

ボタンのグループを2つに分ける

ボタンのグループを、.nav__inner--left.nav__inner--rightの2つに分けています。

3つのボタン群を作る

ボタン群を数値・小数点演算子削除関連の計3つ作ります。

CSS(SCSS)で電卓の見た目を作る

次に以下のCSSを書いて電卓の見た目を作ります。

@import url(https://fonts.googleapis.com/css?family=Righteous);
* {
    box-sizing: border-box;
}

html {
    font-size: 24px;
    font-size: 4vw;
    @media screen and (min-width: 600px) {
        font-size: 1.5em;
    }
}

body {
    background: #180052;
    font-family: 'Righteous', cursive;
    margin: 0;
    padding: 0;
    text-shadow: -1px -1px #cc6600;
}

%flex-style {
    display: -webkit-flex;
    display: flex;
    -webkit-flex-wrap: wrap;
    flex-wrap: wrap;
}

%gradient {
    background: -webkit-gradient(linear, left top, left bottom, from(#B39DDB), color-stop(50%, #673AB7), color-stop(51%, #512DA8), to(#311B92));
}

.calculator {
    position: absolute;
    top: 50%;
    left: 50%;
    -webkit-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
    width: 12em;
}

.display {
    position: relative;
    z-index: 30;
    &__wrap {
        border-radius: 0.5em;
        padding: 0.1em;
    }
    border-radius: 0.5em;
    @extend %gradient;
    color: #fff;
    display: block;
    padding: 1em;
    transition: color 0.3s ease;
    text-align: right;
    text-decoration: none;
    &__sym {
        height: 1em;
    }
}

.nav {
    @extend %flex-style;
    &__inner {
        @extend %flex-style;
        &--left {
            width: 9em;
        }
        &--right {
            width: 3em;
        }
    }
    &__erase {
        @extend %flex-style;
    }
    &__num {
        @extend %flex-style;
    }
    &__btn {
        @extend %gradient;
        border-radius: 0.5em;
        color: #fff;
        display: block;
        height: 3em;
        line-height: 3em;
        transition: color 0.3s ease;
        text-align: center;
        text-decoration: none;
        &:hover {
            color: #fff;
        }
        &__wrap {
            padding: 0.1em;
            width: 3em;
        }
    }
}

CSSのポイントは以下の通りです。

flexboxでレイアウトを組む

各ボタンを敷き詰めるためにそれぞれの親要素にdisplay:flex;を指定しています。更に、ボタンを複数行に折り返すために、flex-wrap:wrap;を指定しています。

グラデーションで立体感を表現

グラデーションでディスプレイとボタンを立体的に表現します。

可変にする

htmlfont-sizeの単位をvw(vh)とし、各要素の単位をemとすることでウィンドウの幅の変化に応じて電卓が自動で可変されるようにします。

JavaScript(TypeScript)で電卓を機能させる

最後に以下のJavaScriptを書いて電卓を機能させます。

(() => {
    class Btn {
        private operators: string[];
        private _erase: NodeList;
        private _num: NodeList;
        private _operator: NodeList;

        constructor() {

            //数値・小数点のボタン要素
            this._num = document.querySelectorAll('.nav__btn--num');

            //削除に関するボタン要素
            this._erase = document.querySelectorAll('.nav__btn--erase');

            //演算子のボタン要素
            this._operator = document.querySelectorAll('.nav__btn--operator');
        }

        get num(): NodeList {
            return this._num;
        }

        get erase(): NodeList {
            return this._erase;
        }

        get operator(): NodeList {
            return this._operator;
        }

        //押したボタンは=かどうか
        public isEqual(value): boolean {
            if (value == '=') {
                return true;
            }
        }

        //押したボタンはDELかどうか
        public isDEL(value): boolean {
            if (value == 'del') {
                return true;
            }
        }

        //押したボタンはCAかどうか
        public isCA(value): boolean {
            if (value == 'ca') {
                return true;
            }
        }

        //押したボタンはCEかどうか
        public isCE(value): boolean {
            if (value == 'ce') {
                return true;
            }
        }
    }

    class Display {
        private _num: HTMLElement;
        private _symbol: HTMLElement;

        constructor() {

            //数値・小数点を表示するディスプレイ
            this._num = document.getElementById('displayNum');

            //演算子を表示するディスプレイ
            this._symbol = document.getElementById('displaySym');

            //#displayNumは初めは0を表示
            this.outputNum = `${0}`;
        }

        get outputNum(): string {
            return this._num.innerHTML;
        }

        set outputNum(num) {
            this._num.innerHTML = num;
        }

        get outputSymbol(): string {
            return this._symbol.innerHTML;
        }

        set outputSymbol(symbol) {
            this._symbol.innerHTML = symbol;
        }
    }

    class Calculator {
        private linking: string;
        private currentValue: string;
        private total: number;
        private isInputNum: boolean;
        private btn: Btn;
        private display: Display;

        constructor() {

            //合計値と入力値の演算子
            this.linking = '+';

            //現在入力している値
            this.currentValue = '';

            //合計値
            this.total = 0;

            //数値か小数点のボタンを押したかどうか
            this.isInputNum = false;

            //ボタンのオブジェクト
            this.btn = new Btn;

            //ディスプレイのオブジェクト
            this.display = new Display;

            //数値・小数点のボタン
            const num = this.btn.num;

            //削除に関するボタン
            const erase = this.btn.erase;

            //演算子のボタン
            const operator = this.btn.operator;

            //数値・小数点のボタンを押すとinputValueメソッドを実行
            for (let i = 0; i < num.length; i++) {
                num[i].addEventListener('click', (e) => this.inputValue(e));
            }

            //CE・CA・DELのボタンを押すとeraseメソッドを実行
            for (let i = 0; i < erase.length; i++) {
                erase[i].addEventListener('click', (e) => this.erase(e));
            }

            //演算子のボタンを押すとcalculateメソッドを実行
            for (let i = 0; i < operator.length; i++) {
                operator[i].addEventListener('click', (e) => this.calculate(e));
            }   
        }

        private inputValue(e) {

            //ボタンを押したときにアドレスバーに#が表示されないようにする
            e.preventDefault();

            //押したボタンの値を取得
            const target = e.target.getAttribute('data-value');

            //押したボタンの数値又は小数点を連結していく
            this.currentValue += target;

            //押したボタンの数値・小数点をディスプレイに表示
            this.display.outputNum = this.currentValue;

            //数値又は小数点を入力した
            this.isInputNum = true;
        }

        private calculate(e): void {

            //ボタンを押したときにアドレスバーにhrefの#が表示されないようにする
            e.preventDefault();

            //押したボタンの値(演算子)を取得
            const target = e.target.getAttribute('data-value');

            //押したボタンの値(演算子)をディスプレイに表示
            this.display.outputSymbol = target;

            //最後に入力した値が数値か小数点であれば
            if(this.isInputNum) {

                //最後に入力した値は演算子なのでfalseに戻す
                this.isInputNum = false;

                //合計値を算出
                //初回は0 + 現在入力されている値
                this.total = Calculator.calc(this.total, this.linking, parseFloat(this.currentValue));

                //現在入力している値を空にする
                this.currentValue = '';

                //合計値をディスプレイに表示
                this.display.outputNum = `${this.total}`;
            }

            //押した演算子が=だったら
            if(this.btn.isEqual(target)) {

                //ディスプレイから演算子を削除
                this.display.outputSymbol = '';

                //初期状態に戻す
                this.linking = '+';

            //それ以外の演算子だったら
            }else {

                //押されたボタンの演算子を代入
                this.linking = target;
            }
        }

        //入力の削除に関するメソッド
        private erase(e) {
            //ボタンを押したときにアドレスバーにhrefの#が表示されないようにする
            e.preventDefault();

            //押したボタンの値を取得
            const target = e.target.getAttribute('data-value');

            //押したボタンがDELだったら
            if(this.btn.isDEL(target)) {

                //ディスプレイに表示されている数値の最後尾を削除
                const delNum = this.display.outputNum.substr(0, this.display.outputNum.length - 1);

                //最後尾削除後の数値をディスプレイに表示
                this.display.outputNum = delNum;

                //ディスプレイに表示されている数値の文字数が0以下になったら
                if(this.display.outputNum.length <= 0) {

                    //ディスプレイに0を表示
                    this.display.outputNum = `${0}`;

                    //それまで保存されていた入力中の値を空にする
                    this.currentValue = '';
                }
            }

            //押したボタンがCAだったら
            if(this.btn.isCA(target)) {

                //合計を0に戻す
                this.total = 0;

                //合計と入力値の演算子を初期状態に戻す
                this.linking = '+';

                //現在入力している値を削除
                this.currentValue = '';

                //ディスプレイに0を表示
                this.display.outputNum = `${this.total}`;

                //ディスプレイに表示されている演算子を削除
                this.display.outputSymbol = '';
            }

            //押したボタンがCEだったら
            if(this.btn.isCE(target)) {

                //現在入力されている値を削除
                this.currentValue = '';

                //ディスプレイに0を表示
                this.display.outputNum = `${0}`;
            }
        }

        private static calc(a, r, b): number {
            return (r == '+') ? a + b : (r == '-') ? a - b : (r == '*') ? a * b : a / b;
        }
    }

    const calculator = new Calculator;
})();

JavaScriptのポイントは以下の通りです。

Btnクラスを作成

ボタンに関するBtnクラスを作成します。

このクラスでは各ボタンの参照を持ち、また押したボタンの判定も行なっています。

Displayクラスを作成

ディスプレイに関するDisplayクラスを作成します。

このクラスでは各ディスプレイの参照を持ちます。また、初めは演算子を表示するディスプレイoutputNumに0を表示します。

Calculatorクラスを作成

電卓の制御に関するCalculatorクラスを作成します。

このクラスでは入力した値をディスプレイに表示したり、表示されている値の削除や入力した値や演算子を基に計算を行い、その計算結果をディスプレイに表示するといったユーザーの操作に応じた振る舞いを行うようにします。

計算は以下の流れで行われます。

20 + 30 * 2 = を計算してみる

linking = '+';
currentValue = '';
total = '';
isInputNum = false;

1. 20を入力

inputValueメソッドを実行→

  • 現在の入力値currentValue(20)

  • 数値・小数点を表示するディスプレイoutputNum(20)

  • 最後に入力した値は数値・小数点なのでisInputNum(true)

2. +を入力

calculatorメソッドを実行→

  • 演算子を表示するディスプレイoutputSymbol(+)

  • +を入力したのでisInputNum(false)

  • 合計値total(0)

  • 合計値と現在の入力値の演算子linking(+)

  • 現在の入力値currentValue(20)

  • 合計値total(20)

  • 現在の入力値currentValue(”)

  • 数値・小数点を表示するディスプレイoutputNum(20)

  • 押されたボタンの演算子を代入linking(+)

3. 30を入力

inputValueメソッドを実行→

  • 現在の入力値currentValue(30)

  • 数値・小数点を表示するディスプレイoutputNum(30)

  • 最後に入力した値は数値・小数点なのでisInputNum(true)

4. *を入力

calculatorメソッドを実行→

  • 演算子を表示するディスプレイoutputSymbol(*)

  • *を入力したのでisInputNum(false)

  • 合計値total(20)

  • 合計値と現在の入力値の演算子linking(*)

  • 現在の入力値currentValue(30)

  • 合計値total(50)

  • 現在の入力値currentValue(”)

  • 数値・小数点を表示するディスプレイoutputNum(50)

  • 押されたボタンの演算子を代入linking(*)

5. 2を入力

inputValueメソッドを実行→

  • 現在の入力値currentValue(2)

  • 数値・小数点を表示するディスプレイoutputNum(2)

  • 最後に入力した値は数値・小数点なのでisInputNum(true)

6. =を入力

calculatorメソッドを実行→

  • 演算子を表示するディスプレイoutputSymbol(=)

  • =を入力したのでisInputNum(false)

  • 合計値total(50)

  • 合計値と現在の入力値の演算子linking(*)

  • 現在の入力値currentValue(2)

  • 合計値total(100)

  • 現在の入力値currentValue(”)

  • 数値・小数点を表示するディスプレイoutputNum(100)

  • 演算子を表示するディスプレイoutputSymbol(”)

  • 初期状態に戻すlinking(+)

電卓を起動

最後にCalculatorクラスをインスタンス化し、電卓を起動させます。

最後に

以上で、HTML+CSS+JavaScriptによる簡易電卓の作り方を終わります。

参考文献