实战项目3:计算器

项目介绍

在这个项目中,我们将创建一个功能完整的计算器应用,该应用允许用户进行基本的数学运算(加、减、乘、除)以及一些高级功能(平方根、百分比、正负号转换等)。这个项目将帮助我们学习JavaScript的数学运算、DOM操作、事件处理和状态管理等核心概念。

项目需求

  1. 显示当前输入的数字和计算结果
  2. 支持基本的数学运算:加法(+)、减法(-)、乘法(×)、除法(÷)
  3. 支持小数点输入
  4. 支持清除功能(AC)
  5. 支持删除最后一位数字(DEL)
  6. 支持正负号转换(±)
  7. 支持百分比计算(%)
  8. 支持高级数学运算:平方根(√)
  9. 支持连续计算
  10. 处理各种边界情况(如除以零)
  11. 页面布局简洁美观,模拟真实计算器的外观

技术栈

  • HTML5
  • CSS3
  • JavaScript (ES6+)

项目结构

calculator-project/
├── index.html      # 主页面
├── styles.css      # 样式文件
└── script.js       # JavaScript文件

实现步骤

1. 创建HTML结构

首先,我们需要创建HTML结构,包括计算器的显示屏和按钮。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算器</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="calculator-container">
        <div class="calculator">
            <!-- 显示屏 -->
            <div class="display">
                <div class="previous-operand" id="previous-operand"></div>
                <div class="current-operand" id="current-operand">0</div>
            </div>
            
            <!-- 按钮区域 -->
            <div class="buttons">
                <!-- 第一行:AC, DEL, %, ÷ -->
                <button class="btn btn-clear" id="clear">AC</button>
                <button class="btn btn-delete" id="delete">DEL</button>
                <button class="btn btn-operator" data-operator="%">%</button>
                <button class="btn btn-operator" data-operator="/">÷</button>
                
                <!-- 第二行:7, 8, 9, × -->
                <button class="btn btn-number" data-number="7">7</button>
                <button class="btn btn-number" data-number="8">8</button>
                <button class="btn btn-number" data-number="9">9</button>
                <button class="btn btn-operator" data-operator="*">×</button>
                
                <!-- 第三行:4, 5, 6, - -->
                <button class="btn btn-number" data-number="4">4</button>
                <button class="btn btn-number" data-number="5">5</button>
                <button class="btn btn-number" data-number="6">6</button>
                <button class="btn btn-operator" data-operator="-">-</button>
                
                <!-- 第四行:1, 2, 3, + -->
                <button class="btn btn-number" data-number="1">1</button>
                <button class="btn btn-number" data-number="2">2</button>
                <button class="btn btn-number" data-number="3">3</button>
                <button class="btn btn-operator" data-operator="+">+</button>
                
                <!-- 第五行:±, 0, ., = -->
                <button class="btn btn-function" id="toggle-sign">±</button>
                <button class="btn btn-number" data-number="0">0</button>
                <button class="btn btn-number" data-number=".">.</button>
                <button class="btn btn-equals" id="equals">=</button>
                
                <!-- 第六行:平方根 -->
                <button class="btn btn-function btn-wide" data-function="sqrt">√</button>
            </div>
        </div>
    </div>
    
    <script src="script.js"></script>
</body>
</html>

2. 添加CSS样式

接下来,我们需要添加CSS样式,使计算器应用看起来更加美观和真实。

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Arial', sans-serif;
    background-color: #f0f0f0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    color: #333;
}

.calculator-container {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100%;
}

.calculator {
    background-color: #2c3e50;
    border-radius: 20px;
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
    padding: 20px;
    width: 320px;
    max-width: 100%;
}

/* 显示屏样式 */
.display {
    background-color: #34495e;
    border-radius: 10px;
    padding: 20px;
    margin-bottom: 20px;
    text-align: right;
    min-height: 100px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    overflow: hidden;
}

.previous-operand {
    color: rgba(255, 255, 255, 0.7);
    font-size: 18px;
    margin-bottom: 5px;
    word-break: break-all;
}

.current-operand {
    color: white;
    font-size: 48px;
    font-weight: bold;
    word-break: break-all;
}

/* 按钮区域样式 */
.buttons {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-gap: 12px;
}

.btn {
    background-color: #34495e;
    color: white;
    border: none;
    border-radius: 50%;
    width: 60px;
    height: 60px;
    font-size: 20px;
    font-weight: bold;
    cursor: pointer;
    transition: all 0.2s ease;
    display: flex;
    justify-content: center;
    align-items: center;
    user-select: none;
}

.btn:hover {
    background-color: #3a536b;
    transform: scale(1.05);
}

.btn:active {
    transform: scale(0.95);
    box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.3);
}

/* 特殊按钮样式 */
.btn-operator {
    background-color: #e67e22;
}

.btn-operator:hover {
    background-color: #d35400;
}

.btn-equals {
    background-color: #27ae60;
}

.btn-equals:hover {
    background-color: #229954;
}

.btn-clear, .btn-delete {
    background-color: #e74c3c;
}

.btn-clear:hover, .btn-delete:hover {
    background-color: #c0392b;
}

.btn-function {
    background-color: #9b59b6;
}

.btn-function:hover {
    background-color: #8e44ad;
}

/* 宽按钮样式 */
.btn-wide {
    grid-column: span 2;
    border-radius: 30px;
    width: 100%;
}

/* 响应式设计 */
@media (max-width: 480px) {
    .calculator {
        width: 280px;
        padding: 15px;
    }
    
    .btn {
        width: 55px;
        height: 55px;
        font-size: 18px;
    }
    
    .buttons {
        grid-gap: 10px;
    }
    
    .current-operand {
        font-size: 40px;
    }
}

@media (max-width: 320px) {
    .calculator {
        width: 240px;
        padding: 10px;
    }
    
    .btn {
        width: 50px;
        height: 50px;
        font-size: 16px;
    }
    
    .buttons {
        grid-gap: 8px;
    }
    
    .current-operand {
        font-size: 32px;
    }
}

3. 编写JavaScript逻辑

最后,我们需要编写JavaScript逻辑,实现计算器的所有功能。

// 获取DOM元素
const previousOperandElement = document.getElementById('previous-operand');
const currentOperandElement = document.getElementById('current-operand');
const numberButtons = document.querySelectorAll('[data-number]');
const operatorButtons = document.querySelectorAll('[data-operator]');
const functionButtons = document.querySelectorAll('[data-function]');
const equalsButton = document.getElementById('equals');
const clearButton = document.getElementById('clear');
const deleteButton = document.getElementById('delete');
const toggleSignButton = document.getElementById('toggle-sign');

// 计算器状态
let currentOperand = '0';
let previousOperand = '';
let operation = undefined;
let shouldResetCurrentOperand = false;

// 更新显示屏
function updateDisplay() {
    currentOperandElement.textContent = formatNumber(currentOperand);
    if (operation != null) {
        previousOperandElement.textContent = `${formatNumber(previousOperand)} ${getOperatorSymbol(operation)}`;
    } else {
        previousOperandElement.textContent = '';
    }
}

// 格式化数字显示
function formatNumber(number) {
    const stringNumber = number.toString();
    const integerDigits = parseFloat(stringNumber.split('.')[0]);
    const decimalDigits = stringNumber.split('.')[1];
    let integerDisplay;
    
    if (isNaN(integerDigits)) {
        integerDisplay = '';
    } else {
        integerDisplay = integerDigits.toLocaleString('en', { maximumFractionDigits: 0 });
    }
    
    if (decimalDigits != null) {
        return `${integerDisplay}.${decimalDigits}`;
    } else {
        return integerDisplay;
    }
}

// 获取操作符符号
function getOperatorSymbol(operator) {
    switch (operator) {
        case '+':
            return '+';
        case '-':
            return '-';
        case '*':
            return '×';
        case '/':
            return '÷';
        default:
            return '';
    }
}

// 输入数字
function appendNumber(number) {
    if (shouldResetCurrentOperand) {
        currentOperand = number;
        shouldResetCurrentOperand = false;
    } else {
        currentOperand = currentOperand === '0' ? number : currentOperand + number;
    }
}

// 添加小数点
function appendDecimal() {
    if (shouldResetCurrentOperand) {
        currentOperand = '0.';
        shouldResetCurrentOperand = false;
    } else if (!currentOperand.includes('.')) {
        currentOperand += '.';
    }
}

// 选择操作符
function chooseOperation(nextOperation) {
    if (currentOperand === '') return;
    
    if (previousOperand !== '') {
        compute();
    }
    
    operation = nextOperation;
    previousOperand = currentOperand;
    currentOperand = '';
}

// 执行计算
function compute() {
    let computation;
    const prev = parseFloat(previousOperand);
    const current = parseFloat(currentOperand);
    
    if (isNaN(prev) || isNaN(current)) return;
    
    switch (operation) {
        case '+':
            computation = prev + current;
            break;
        case '-':
            computation = prev - current;
            break;
        case '*':
            computation = prev * current;
            break;
        case '/':
            if (current === 0) {
                computation = 'Error';
            } else {
                computation = prev / current;
            }
            break;
        default:
            return;
    }
    
    currentOperand = computation.toString();
    operation = undefined;
    previousOperand = '';
    shouldResetCurrentOperand = true;
}

// 清除所有
function clear() {
    currentOperand = '0';
    previousOperand = '';
    operation = undefined;
    shouldResetCurrentOperand = false;
}

// 删除最后一位
function deleteNumber() {
    if (shouldResetCurrentOperand) {
        return;
    }
    
    if (currentOperand.length === 1) {
        currentOperand = '0';
    } else {
        currentOperand = currentOperand.slice(0, -1);
    }
}

// 转换正负号
function toggleSign() {
    if (currentOperand === '0' || currentOperand === 'Error') return;
    
    if (currentOperand.startsWith('-')) {
        currentOperand = currentOperand.slice(1);
    } else {
        currentOperand = '-' + currentOperand;
    }
}

// 计算百分比
function calculatePercentage() {
    const current = parseFloat(currentOperand);
    if (isNaN(current)) return;
    
    currentOperand = (current / 100).toString();
    shouldResetCurrentOperand = true;
}

// 计算平方根
function calculateSquareRoot() {
    const current = parseFloat(currentOperand);
    if (isNaN(current)) return;
    
    if (current < 0) {
        currentOperand = 'Error';
    } else {
        currentOperand = Math.sqrt(current).toString();
    }
    
    shouldResetCurrentOperand = true;
}

// 处理函数按钮
function handleFunction(functionType) {
    switch (functionType) {
        case 'sqrt':
            calculateSquareRoot();
            break;
        default:
            break;
    }
}

// 添加事件监听器
numberButtons.forEach(button => {
    button.addEventListener('click', () => {
        if (button.dataset.number === '.') {
            appendDecimal();
        } else {
            appendNumber(button.dataset.number);
        }
        updateDisplay();
    });
});

operatorButtons.forEach(button => {
    button.addEventListener('click', () => {
        if (button.dataset.operator === '%') {
            calculatePercentage();
        } else {
            chooseOperation(button.dataset.operator);
        }
        updateDisplay();
    });
});

functionButtons.forEach(button => {
    button.addEventListener('click', () => {
        handleFunction(button.dataset.function);
        updateDisplay();
    });
});

equalsButton.addEventListener('click', () => {
    compute();
    updateDisplay();
});

clearButton.addEventListener('click', () => {
    clear();
    updateDisplay();
});

deleteButton.addEventListener('click', () => {
    deleteNumber();
    updateDisplay();
});

toggleSignButton.addEventListener('click', () => {
    toggleSign();
    updateDisplay();
});

// 添加键盘支持
document.addEventListener('keydown', (event) => {
    // 数字键
    if (event.key >= '0' && event.key <= '9') {
        appendNumber(event.key);
        updateDisplay();
    }
    
    // 小数点
    if (event.key === '.') {
        appendDecimal();
        updateDisplay();
    }
    
    // 操作符
    if (event.key === '+' || event.key === '-' || event.key === '*' || event.key === '/') {
        chooseOperation(event.key);
        updateDisplay();
    }
    
    // 回车键和等号键
    if (event.key === 'Enter' || event.key === '=') {
        compute();
        updateDisplay();
    }
    
    // 退格键
    if (event.key === 'Backspace') {
        deleteNumber();
        updateDisplay();
    }
    
    // Escape键
    if (event.key === 'Escape') {
        clear();
        updateDisplay();
    }
});

// 初始更新显示
updateDisplay();

功能说明

  1. 数字输入:点击数字按钮或使用键盘数字键输入数字。
  2. 基本运算:支持加法(+)、减法(-)、乘法(×)、除法(÷)。
  3. 小数点输入:点击小数点按钮或使用键盘小数点键输入小数点。
  4. 清除功能:点击"AC"按钮或按"Escape"键清除所有内容。
  5. 删除功能:点击"DEL"按钮或按"Backspace"键删除最后一位数字。
  6. 正负号转换:点击"±"按钮可以转换当前数字的正负号。
  7. 百分比计算:点击"%"按钮可以将当前数字转换为百分比。
  8. 平方根计算:点击"√"按钮可以计算当前数字的平方根。
  9. 连续计算:支持连续进行多个运算,例如:1 + 2 × 3。
  10. 边界情况处理:处理除以零等边界情况,显示"Error"。
  11. 键盘支持:支持使用键盘进行所有操作。

扩展功能

我们可以进一步扩展计算器的功能,例如:

  1. 支持更多数学函数:正弦、余弦、正切、对数等。
  2. 支持括号运算:允许用户输入括号来改变运算优先级。
  3. 支持记忆功能:M+、M-、MR、MC等记忆功能。
  4. 支持历史记录:显示之前的计算历史。
  5. 支持不同的计算模式:标准模式、科学模式、程序员模式。
  6. 支持主题切换:深色/浅色主题切换。
  7. 支持声音反馈:点击按钮时播放声音。

扩展功能示例:更多数学函数

<!-- 在HTML中添加更多函数按钮 -->
<!-- 第七行:sin, cos, tan, log -->
<button class="btn btn-function" data-function="sin">sin</button>
<button class="btn btn-function" data-function="cos">cos</button>
<button class="btn btn-function" data-function="tan">tan</button>
<button class="btn btn-function" data-function="log">log</button>
// 在handleFunction函数中添加更多数学函数处理
function handleFunction(functionType) {
    const current = parseFloat(currentOperand);
    if (isNaN(current)) return;
    
    switch (functionType) {
        case 'sqrt':
            currentOperand = current < 0 ? 'Error' : Math.sqrt(current).toString();
            break;
        case 'sin':
            currentOperand = Math.sin(current * Math.PI / 180).toString(); // 转换为角度
            break;
        case 'cos':
            currentOperand = Math.cos(current * Math.PI / 180).toString(); // 转换为角度
            break;
        case 'tan':
            currentOperand = Math.tan(current * Math.PI / 180).toString(); // 转换为角度
            break;
        case 'log':
            currentOperand = current <= 0 ? 'Error' : Math.log10(current).toString();
            break;
        default:
            break;
    }
    
    shouldResetCurrentOperand = true;
}

扩展功能示例:记忆功能

<!-- 在HTML中添加记忆功能按钮 -->
<!-- 第八行:M+, M-, MR, MC -->
<button class="btn btn-function" data-function="m-plus">M+</button>
<button class="btn btn-function" data-function="m-minus">M-</button>
<button class="btn btn-function" data-function="m-recall">MR</button>
<button class="btn btn-function" data-function="m-clear">MC</button>
// 添加记忆功能
let memory = 0;

// 在handleFunction函数中添加记忆功能处理
function handleFunction(functionType) {
    const current = parseFloat(currentOperand);
    
    switch (functionType) {
        // 其他函数处理...
        
        // 记忆功能
        case 'm-plus':
            if (!isNaN(current)) {
                memory += current;
            }
            break;
        case 'm-minus':
            if (!isNaN(current)) {
                memory -= current;
            }
            break;
        case 'm-recall':
            currentOperand = memory.toString();
            shouldResetCurrentOperand = true;
            break;
        case 'm-clear':
            memory = 0;
            break;
        
        default:
            break;
    }
    
    shouldResetCurrentOperand = true;
}

项目运行

  1. 创建上述三个文件(index.html, styles.css, script.js)
  2. 在浏览器中打开index.html文件
  3. 开始使用计算器应用

项目总结

通过这个计算器项目,我们学习了:

  1. 如何使用JavaScript进行数学运算
  2. 如何处理各种边界情况(如除以零)
  3. 如何使用DOM操作更新页面内容
  4. 如何添加和处理各种事件
  5. 如何实现状态管理
  6. 如何支持键盘操作
  7. 如何实现响应式设计

这个项目涵盖了JavaScript开发中的许多重要概念,为我们后续开发更复杂的应用打下了基础。

练习

  1. 添加更多数学函数:正弦(sin)、余弦(cos)、正切(tan)、对数(log)等。
  2. 实现括号运算功能,允许用户输入括号来改变运算优先级。
  3. 添加记忆功能:M+、M-、MR、MC等。
  4. 添加计算历史记录功能,显示之前的计算历史。
  5. 实现主题切换功能,支持深色/浅色主题。
  6. 添加声音反馈功能,点击按钮时播放声音。
  7. 支持不同的计算模式:标准模式、科学模式、程序员模式。
  8. 实现表达式求值功能,允许用户输入完整的数学表达式。

扩展阅读

通过这个实战项目,我们已经掌握了JavaScript的数学运算和DOM操作,接下来我们将继续学习更复杂的JavaScript项目。

« 上一篇 实战项目2:待办事项列表 下一篇 » 实战项目4:问答系统