# 如何用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(); }; ``` 好了,具體上來說大概就這樣,有問題就問學長姐吧,我應該答不出來。