# 如何用JS做個計算器
今天要來講的是用`JavaScript`為網頁上的計算器賦予它計算的功能。
我這邊是直接還原IPhone開啟內建計算器時的樣子。
因此除了計算的功能之外,我也添加了一個顯示當下時間的功能。
[成品](https://reurl.cc/GXe11Z)
## HTML的部分
下面這邊是`HTML`中所有的代碼,我接下來分段落解說。
```html
<div class="calculator">
<div class="top-container">
<div class="clock">
<span class="hour"></span>:<span class="minute"></span>
</div>
<div class="status">
<img src="status.png" alt="Status">
</div>
</div>
<div class="value">0</div>
<div class="buttons-container">
<div class="button function ac">AC</div>
<div class="button function pm">±</div>
<div class="button function percent">%</div>
<div class="button operator division">÷</div>
<div class="button number-7">7</div>
<div class="button number-8">8</div>
<div class="button number-9">9</div>
<div class="button operator multiplication">×</div>
<div class="button number-4">4</div>
<div class="button number-5">5</div>
<div class="button number-6">6</div>
<div class="button operator subtraction">−</div>
<div class="button number-1">1</div>
<div class="button number-2">2</div>
<div class="button number-3">3</div>
<div class="button operator addition">+</div>
<div class="button number-0">0</div>
<div class="button decimal">.</div>
<div class="button operator equal">=</div>
</div>
<div class="bottom"></div>
</div>
```
### 時間
這邊是左上角的`時間`顯示。
```html
<div class="clock">
<span class="hour"></span>:<span class="minute"></span>
</div>
```
### 網路訊號與電量
這邊是右上角的`網路訊號`與`電量`顯示。
這部分我能力不足,所以就用了張圖片稍微表示了一下。
```html
<div class="status">
<img src="status.png" alt="Status">
</div>
```
### 運算數值
這邊是`運算數值`顯示。
```html
<div class="value">0</div>
```
### 按鍵
這邊是`按鍵`顯示。
```html
<div class="button function ac">AC</div>
<div class="button function pm">±</div>
<div class="button function percent">%</div>
<div class="button operator division">÷</div>
<div class="button number-7">7</div>
<div class="button number-8">8</div>
<div class="button number-9">9</div>
<div class="button operator multiplication">×</div>
<div class="button number-4">4</div>
<div class="button number-5">5</div>
<div class="button number-6">6</div>
<div class="button operator subtraction">−</div>
<div class="button number-1">1</div>
<div class="button number-2">2</div>
<div class="button number-3">3</div>
<div class="button operator addition">+</div>
<div class="button number-0">0</div>
<div class="button decimal">.</div>
<div class="button operator equal">=</div>
```
### 底部白色橫條
這邊是`底部白色橫條`顯示。
```html
<div class="bottom"></div>
```
## CSS的部分
CSS的部分應該就不用多講了,這邊沒用到甚麼太特別的屬性。
像`transform`這類的之前也都有講過,所以就不再多提了。
這邊特別一點的應該就`grid網格布局`吧。
我下面這邊就拉出來講一下。
```css
*{
box-sizing:border-box;
margin:0;
padding:0;
user-select:none;
}
body{
font-family:"Helvetica Neue",sans-serif;
margin:25px;
}
.calculator{
background:black;
border-radius:50px;
color:white;
height:1218px;
padding:20px;
position:relative;
width:563px;
}
.top-container{
display:flex;
height:250px;
justify-content:space-between;
padding:0 20px;
}
.value{
font-size:130px;
font-weight:300;
height:158px;
margin-bottom:20px;
margin-right:20px;
text-align:right;
}
.buttons-container{
display:grid;
grid-gap:20px;
grid-template-columns:repeat(4,1fr);
grid-template-rows:repeat(5,1fr);
}
.button{
align-items:center;
background:#333;
border-radius:50%;
cursor:pointer;
display:flex;
font-size:45px;
height:110px;
justify-content:center;
transition:filter .3s;
width:110px;
}
.button.function{
color:black;
background:#a5a5a5;
}
.button.operator{
background:#f1a33c;
}
.button.number-0{
border-radius:55px;
grid-column:1 / span 2;
justify-content:flex-start;
padding-left:43px;
width:250px;
}
.button:active,
.button:focus{
filter:brightness(120%);
}
.bottom{
width:200px;
height:5px;
background:white;
border-radius:4px;
position:absolute;
bottom:10px;
left:50%;
transform:translateX(-50%);
}
```
### grid網格布局
這邊我用`grid`的原因是我覺得用`flex`的話太麻煩了,要造出很多的`div`。
之前剛好網路上找教學時看到有人用這個東西,所以就想說拿來用一下。
從下面這段代碼,可以看到說我先設了一個`20px`的間距,套用在每一個網格。
然後再將它分成`5行4列`。
```css
.buttons-container{
display:grid;
grid-gap:20px;
grid-template-columns:repeat(4,1fr);
grid-template-rows:repeat(5,1fr);
}
```
當然,`grid布局`不只有這樣,它可以對每條網線做到更多的調整。
[這張圖片](https://i0.wp.com/yukihiew.com/wp-content/uploads/2021/08/2.600x400.png?w=601&ssl=1)可以讓你看個大概。
我是看這篇[文章](https://yukihiew.com/about-css-grid/#:~:text=%E5%BC%B7%E5%A4%A7%E7%9A%84CSS%20grid%E7%B6%B2%E6%A0%BC%E6%8E%92%E7%89%88-%E4%BB%8B%E7%B4%B9%E8%88%87%E6%87%89%E7%94%A8%201%201.%E7%B0%A1%E5%96%AE%E8%AA%8D%E8%AD%98CSS%20grid%20%E7%B6%B2%E6%A0%BC%E7%B3%BB%E7%B5%B1%20grid%E6%98%AFcss%E4%B8%AD%E5%A5%BD%E7%94%A8%E7%9A%84%E6%8E%92%E7%89%88%E6%96%B9%E5%BC%8F%EF%BC%8C%E9%80%8F%E9%81%8E%E8%A8%AD%E5%AE%9A%E6%AC%84%EF%BC%88column%29%E5%88%97%20%28row%29%E4%BE%86%E9%81%94%E6%88%90%E5%83%8Fexcel%E9%82%A3%E6%A8%A3%E7%9A%84%E7%B6%B2%E6%A0%BC%E6%8E%92%E7%89%88%E3%80%82,%E5%B8%83%E7%BD%AE%E5%A5%BD%E5%85%A7%E5%AE%B9%E5%BE%8C%EF%BC%8C%E5%B0%B1%E8%A9%B2%E8%AA%BF%E6%95%B4%E7%B4%B0%E9%83%A8%E5%95%A6%21%20grid%E4%B8%80%E6%A8%A3%E4%B9%9F%E6%9C%89%E8%AA%BF%E6%95%B4%E9%96%93%E9%9A%94%E7%9A%84%E5%B1%AC%E6%80%A7%EF%BC%8C%E4%BB%A5%E5%8F%8A%E8%87%AA%E8%BA%AB%E5%B0%8D%E9%BD%8A%E8%88%87%E6%95%B4%E9%AB%94%E5%B0%8D%E9%BD%8A%E7%9A%84%E8%A8%AD%E5%AE%9A%E5%96%94%21%20...%204%205.%E7%AF%84%E4%BE%8B%26grid%E5%B0%8F%E9%81%8A%E6%88%B2%20%E9%80%99%E9%82%8A%E6%8E%A8%E8%96%A6%E4%B8%80%E5%80%8B%E9%97%9C%E6%96%BCcss%20grid%E7%9A%84%E5%B0%8F%E9%81%8A%E6%88%B2%EF%BC%8C%E9%80%8F%E9%81%8E%E8%A8%AD%E5%AE%9A%E6%A0%BC%E7%B7%9A%E4%BE%86%E6%BE%86%E8%8A%B1%E5%92%8C%E9%99%A4%E8%8D%89%EF%BC%8C%E6%9C%83%E5%B8%B6%E4%BD%A0%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5%E4%BA%86%E8%A7%A3grid%E7%9A%84%E7%94%A8%E6%B3%95%E3%80%82%20)學的。
想練習的話,文章作者有寫一個[範例](https://codepen.io/yukiyin/pen/abqweRL),你可以在裡面調整代碼看看有甚麼變化。
這裡還有一個她推薦練習`grid布局`的[小遊戲](https://cssgridgarden.com/)。
## JavaScript的部分
接下來就是`JavaScript`的部分,我一樣依照順序講解。
### 時間功能
我先做畫面左上角的`時間`。
首先,我們要獲取`HTML`中的`時`和`分`元素。
```javascript
// DOM Elements
const hourEl=document.querySelector('.hour');
const minuteEl=document.querySelector('.minute');
```
接下來寫一個`updateTime`的功能。
```javascript
// Set up the time
const updateTime=()=>{
const currentTime=new Date(); // 初始化日期
let currentHour=currentTime.getHours(); // 抓現在幾時
const currentMinute=currentTime.getMinutes(); // 抓現在幾分
if(currentHour>12){ // 表達成12小時進制
currentHour-=12;
}
hourEl.textContent=currentHour.toString(); // 把原本的小時用新的替換掉
minuteEl.textContent=currentMinute.toString().padStart(2,'0'); // 把原本的分鐘用新的替換掉,並自動補齊兩位數
}
```
最後不要忘記,讓它每秒重新跑一次。
```javascript
// Set up the time
setInterval(updateTime, 1000);
```
一開始也要記得呼叫它出來。
```javascript
// Set up the time
updateTime();
```
這樣`時間`的部分就大功告成了。
### 計算器功能
接下來來做`計算器`的功能。
一樣的,我們先獲取每個`按鍵`的元素。
```javascript
// DOM Elements
const acEl=document.querySelector('.ac');
const pmEl=document.querySelector('.pm');
const percentEl=document.querySelector('.percent');
const additionEl=document.querySelector('.addition');
const subtractionEl=document.querySelector('.subtraction');
const multiplicationEl=document.querySelector('.multiplication');
const divisionEl=document.querySelector('.division');
const equalEl=document.querySelector('.equal');
const decimalEl=document.querySelector('.decimal');
const number0El=document.querySelector('.number-0');
const number1El=document.querySelector('.number-1');
const number2El=document.querySelector('.number-2');
const number3El=document.querySelector('.number-3');
const number4El=document.querySelector('.number-4');
const number5El=document.querySelector('.number-5');
const number6El=document.querySelector('.number-6');
const number7El=document.querySelector('.number-7');
const number8El=document.querySelector('.number-8');
const number9El=document.querySelector('.number-9');
```
來造一個`陣列`把我們每個`number按鍵`放進去,這樣的話在等等後續操作處理會方便一些。
```javascript
// DOM Elements
const numberElArray=[
number0El, number1El, number2El, number3El, number4El,
number5El, number6El, number7El, number8El, number9El
];
```
我們接著先把大致架構寫出來。
第一步,第一個事件就是接收`數字按鈕點擊`。
```javascript
// Add Event Listeners to numbers and decimal
for(let i=0;i<numberElArray.length;i++){
const numberEl=numberElArray[i];
numberEl.addEventListener('click',()=>{ // 這就是為何把number按鍵塞進陣列的原因,在Listen時就不需要一個個寫出來
handleNumberClick(i.toString()); // 把點擊的按鈕代表的數值丟去處理
});
}
```
第二步,來寫`數字按鈕點擊的處理`。
```javascript
// Functions
const getValueAsStr=()=>valueEl.textContent.split(',').join(''); // 要讓字符串每三個位數就用逗號分隔,split分割,join把數組中的所有元素放入一個字符串。元素是通過指定的分隔符進行分隔的。
const handleNumberClick=(numStr)=>{
const currentValueStr=getValueAsStr();
if(currentValueStr==='0'){ //因為它是一個個字串,我們在這判斷是否輸入過別的數,有就把這幾個字串接起來
setStrAsValue(numStr); //這功能細節等等再回來說
}
else{
setStrAsValue(currentValueStr+numStr);
}
};
```
我們回過頭來處理`十進制與小數點`的部分。
```javascript
// Add Event Listeners to numbers and decimal
decimalEl.addEventListener('click',()=>{
const currentValueStr=getValueAsStr();
if(!currentValueStr.includes('.')){ // 之前沒有小數點的話那就給它一個
setStrAsValue(currentValueStr+'.');
}
});
```
處理好後,我們把前面提到的`setStrAsValue`的細節寫完。
```javascript
// Functions
const setStrAsValue=(valueStr)=>{
if(valueStr[valueStr.length-1]==='.'){ // 你要小數點那就給你加上吧
valueEl.textContent+='.';
return;
}
const[wholeNumStr,decimalStr]=valueStr.split('.'); // 處理浮點數,這邊非常直觀
if(decimalStr){
valueEl.textContent=parseFloat(wholeNumStr).toLocaleString()+'.'+decimalStr;
}
else{
valueEl.textContent=parseFloat(wholeNumStr).toLocaleString(); // toLocaleString讓字符串每三位用逗號分隔
}
};
```
之後的步驟就非常簡單了。
第三步,處理完`number按鍵`那就先來處理`function按鍵`吧。
```javascript
// Add Event Listeners to functions
acEl.addEventListener('click',()=>{ // 歸零鍵
setStrAsValue('0');
valueStrInMemory=null; // 暫存的值
operatorInMemory=null; // 暫存的運算子
});
pmEl.addEventListener('click',()=>{ // 正負號
const currentValueNum=getValueAsNum(); // 畫面上的數值
const currentValueStr=getValueAsStr();
if(currentValueStr==='-0'){
setStrAsValue('0');
return;
}
if(currentValueNum >= 0){
setStrAsValue('-'+currentValueStr);
}
else{
setStrAsValue(currentValueStr.substring(1)); // 去掉第一個元素,簡單來說就是把負號拔掉
}
});
percentEl.addEventListener('click',()=>{ // 百分比
const currentValueNum=getValueAsNum();
const newValueNum=currentValueNum/100;
setStrAsValue(newValueNum.toString());
valueStrInMemory=null;
operatorInMemory=null;
});
```
不要忘記把`變量`補上去。
```javascript
// variables
let valueStrInMemory=null;
let operatorInMemory=null;
```
也不要忘記把`getValueAsNum`補上去。
```javascript
// Functions
const getValueAsNum=()=>{
return parseFloat(getValueAsStr());
};
```
第四步,處理完`function按鍵`那就再來處理`operator按鍵`吧。
```javascript
// Add event listeners to operators
additionEl.addEventListener('click',()=>{ // 加
handleOperatorClick('addition');
});
subtractionEl.addEventListener('click',()=>{ // 減
handleOperatorClick('subtraction');
});
multiplicationEl.addEventListener('click',()=>{ // 乘
handleOperatorClick('multiplication');
});
divisionEl.addEventListener('click',()=>{ // 除
handleOperatorClick('division');
});
equalEl.addEventListener('click',()=>{ // 等於
if(valueStrInMemory){
setStrAsValue(getResultOfOperationAsStr()); // getResultOfOperationAsStr也就是運算,等等最後來做
valueStrInMemory=null;
operatorInMemory=null;
}
});
```
第五步,就像`數字按鈕點擊的處理`一樣,來寫一下`運算子按鈕點擊的處理`。
```javascript
// Functions
const handleOperatorClick=(operation)=>{
const currentValueStr=getValueAsStr();
if(!valueStrInMemory){ // 暫存的值是空的話
valueStrInMemory=currentValueStr;
operatorInMemory=operation;
setStrAsValue('0');
return;
}
valueStrInMemory=getResultOfOperationAsStr(); // 最後一步
operatorInMemory=operation;
setStrAsValue('0');
};
```
終於,最後一步,我叫你`算`。
```javascript
const getResultOfOperationAsStr=()=>{
const currentValueNum=getValueAsNum();
const valueNumInMemory=parseFloat(valueStrInMemory);
let newValueNum;
if(operatorInMemory==='addition'){ // 加
newValueNum=valueNumInMemory+currentValueNum;
}
else if(operatorInMemory==='subtraction'){ // 減
newValueNum=valueNumInMemory-currentValueNum;
}
else if(operatorInMemory==='multiplication'){ // 乘
newValueNum=valueNumInMemory*currentValueNum;
}
else if(operatorInMemory==='division'){ // 除
newValueNum=valueNumInMemory/currentValueNum;
}
return newValueNum.toString();
};
```
好了,具體上來說大概就這樣,有問題就問學長姐吧,我應該答不出來。