2024-12-04 13:40
一、Calculator:
【React】基于 styled-components 拟物3D按键效果的计算器
#代码仓库:
GitHub:https://github.com/Tully-L/React-Calculator.git#AI小黑工-Cusor
二、TodoList参考:
https://www.bilibili.com/video/BV1kH4y117yr?vd_source=ab086d60b3f40acfef7c0c8805cdbd40
#代码仓库:
GitHub:https://github.com/Archimesons/react-bilibili-tutorial
#参考资料:
1、运行时环境Node.js/npm下载入口:https://nodejs.org/en
2、IDE Visual Studio Code (vscode) 下载入口:https://code.visualstudio.com/
2、React.js中文文档:https://zh-hans.react.dev/learn
3、JavaScript中文文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
三、实现截图:
第0版-第1版-第二版/最终版 #已实现基本功能
四、React-Calculator代码
1.项目结构
1. **创建项目**
首先,打开命令行,执行以下命令:
```bash
cd E:\React
npx create-react-app react-calculator
cd react-calculator
npm start
```
2. **安装所需依赖**
```bash
npm install styled-components
```
3. **文件结构**
```
react-calculator/
├── src/
│ ├── components/
│ │ ├── Calculator.js
│ │ ├── Button.js
│ │ └── styles.js
│ ├── App.js
│ └── index.js
└── public/
└── index.html
```
2.完整代码
```javascript:src/components/styles.js
import styled from 'styled-components';
export const CalculatorContainer = styled.div`
width: 340px;
margin: 100px auto;
background: #1a1a1a;
padding: 25px;
border-radius: 15px;
box-shadow:
0 20px 40px rgba(0,0,0,0.6),
inset 0 2px 0 rgba(255,255,255,0.1),
inset 0 -1px 0 rgba(0,0,0,0.8);
border: 2px solid #333;
`;
export const DisplayContainer = styled.div`
background: #202020;
width: 90%;
margin: 0 auto 25px auto;
border-radius: 8px;
border: 1px solid #444;
box-shadow:
inset 0 2px 8px rgba(0,0,0,0.5),
0 1px 0 rgba(255,255,255,0.1);
overflow: hidden;
`;
export const DisplayRow = styled.div`
height: 35px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: ${props => props.primary ? '30px' : '20px'};
font-weight: bold;
font-family: 'Digital-7', Consolas, monospace;
color: ${props => props.primary ? '#00ff00' : '#00bb00'};
text-shadow: 0 0 5px rgba(0,255,0,0.5);
`;
export const ButtonGrid = styled.div`
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
padding: 5px;
background: #1a1a1a;
`;
export const StyledButton = styled.button`
position: relative;
width: 100%;
height: 65px;
border: none;
border-radius: 10px;
cursor: pointer;
outline: none;
font-size: 24px;
font-weight: bold;
color: ${props => props.operator ? '#ff9f43' : '#ffffff'};
background: none;
margin-top: 5px;
// 按键主体
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: ${props => props.operator ? '#444' : '#3a3a3a'};
border-radius: 10px;
border: 2px solid rgba(255,255,255,0.1);
transform: translateY(-8px);
transition: all 0.1s ease-in-out;
box-shadow:
0 8px 0 #222,
0 8px 8px rgba(0,0,0,0.4),
inset 0 2px 2px rgba(255,255,255,0.2),
inset 0 -2px 2px rgba(0,0,0,0.2);
}
// 按键底部阴影
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -10px;
height: 10px;
background: #1a1a1a;
border-radius: 0 0 10px 10px;
box-shadow:
inset 0 -4px 8px rgba(0,0,0,0.5),
0 2px 4px rgba(0,0,0,0.3);
}
span {
position: relative;
z-index: 1;
display: block;
width: 100%;
height: 100%;
line-height: 65px;
text-align: center;
transform: translateY(-8px);
transition: all 0.1s ease-in-out;
text-shadow: 0 -1px 0 rgba(0,0,0,0.8);
}
// 按下效果
&:active::before {
transform: translateY(-2px);
box-shadow:
0 2px 0 #222,
0 2px 4px rgba(0,0,0,0.4),
inset 0 2px 2px rgba(255,255,255,0.2),
inset 0 -2px 2px rgba(0,0,0,0.2);
}
&:active span {
transform: translateY(-2px);
}
// 悬浮效果
&:hover::before {
background: ${props => props.operator ? '#4a4a4a' : '#404040'};
box-shadow:
0 8px 0 #222,
0 8px 10px rgba(0,0,0,0.5),
inset 0 2px 3px rgba(255,255,255,0.2),
inset 0 -2px 2px rgba(0,0,0,0.2);
}
`;
# **Button组件**:
```javascript:src/components/Button.js
import React from 'react';
import { StyledButton } from './styles';
const Button = ({ value, onClick, operator }) => {
return (
<StyledButton operator={operator} onClick={() => onClick(value)}>
{value}
</StyledButton>
);
};
export default Button;
```
#**Calculator组件**:
```javascript:src/components/Calculator.js
import React, { useState } from 'react';
import Button from './buttons';
import { CalculatorContainer, DisplayContainer, DisplayRow, ButtonGrid } from './styles';
const Calculator = () => {
const [primaryDisplay, setPrimaryDisplay] = useState('0');
const [secondaryDisplay, setSecondaryDisplay] = useState('');
const [startNew, setStartNew] = useState(true);
const formatNumber = (num) => {
if (Number.isInteger(num)) return num.toString();
const rounded = Number(parseFloat(num).toFixed(10));
return rounded.toString();
};
const handleNumber = (num) => {
if (startNew) {
setSecondaryDisplay(num === '.' ? '0.' : num);
setStartNew(false);
} else {
if (num === '.') {
const lastNumber = secondaryDisplay.split(/[+\-×÷]/).pop();
if (lastNumber.includes('.')) {
return;
}
if (lastNumber === '') {
setSecondaryDisplay(secondaryDisplay + '0.');
return;
}
}
setSecondaryDisplay(secondaryDisplay + num);
}
};
const handleOperator = (operator, display) => {
setStartNew(false);
// 处理负号的特殊情况
if (display === '-') {
const lastChar = secondaryDisplay.slice(-1);
if (secondaryDisplay === '' || '+-×÷'.includes(lastChar)) {
// 在负号前添加左括号
setSecondaryDisplay(secondaryDisplay + '(-');
return;
}
}
// 如果最后一个字符是运算符,替换它
const lastChar = secondaryDisplay.slice(-1);
if ('+-×÷'.includes(lastChar)) {
setSecondaryDisplay(secondaryDisplay.slice(0, -1) + display);
} else {
// 如果前一个输入是右括号,不需要添加额外的右括号
if (lastChar === ')') {
setSecondaryDisplay(secondaryDisplay + display);
} else {
// 检查是否需要闭合括号
const needClosingBracket = secondaryDisplay.includes('(-') &&
!secondaryDisplay.endsWith(')');
setSecondaryDisplay(secondaryDisplay + (needClosingBracket ? ')' : '') + display);
}
}
};
const handleEqual = () => {
try {
// 检查是否需要在计算前添加最后的右括号
let finalExpression = secondaryDisplay;
if (finalExpression.includes('(-') &&
!finalExpression.endsWith(')') &&
!'+-×÷'.includes(finalExpression.slice(-1))) {
finalExpression += ')';
}
setPrimaryDisplay(finalExpression);
// 替换乘除符号并计算
const expression = finalExpression
.replace(/×/g, '*')
.replace(/÷/g, '/');
const result = eval(expression);
const formattedResult = formatNumber(result);
setSecondaryDisplay(formattedResult);
setStartNew(true);
} catch (error) {
setPrimaryDisplay('Error');
setSecondaryDisplay('');
}
};
const handleClear = () => {
setPrimaryDisplay('0');
setSecondaryDisplay('');
setStartNew(true);
};
return (
<CalculatorContainer>
<DisplayContainer>
<DisplayRow>{primaryDisplay}</DisplayRow>
<DisplayRow primary>{secondaryDisplay || '0'}</DisplayRow>
</DisplayContainer>
<ButtonGrid>
<Button value="7" onClick={handleNumber} />
<Button value="8" onClick={handleNumber} />
<Button value="9" onClick={handleNumber} />
<Button value="÷" onClick={() => handleOperator('/', '÷')} operator />
<Button value="4" onClick={handleNumber} />
<Button value="5" onClick={handleNumber} />
<Button value="6" onClick={handleNumber} />
<Button value="×" onClick={() => handleOperator('*', '×')} operator />
<Button value="1" onClick={handleNumber} />
<Button value="2" onClick={handleNumber} />
<Button value="3" onClick={handleNumber} />
<Button value="-" onClick={() => handleOperator('-', '-')} operator />
<Button value="0" onClick={handleNumber} />
<Button value="." onClick={handleNumber} />
<Button value="=" onClick={handleEqual} operator />
<Button value="+" onClick={() => handleOperator('+', '+')} operator />
<Button value="C" onClick={handleClear} operator />
</ButtonGrid>
</CalculatorContainer>
);
};
export default Calculator;
```
#4. **App组件**:
```javascript:src/App.js
import React from 'react';
import Calculator from './components/Calculator';
function App() {
return (
<div className="App">
<Calculator />
</div>
);
}
export default App;
```
3.运行项目
在命令行中执行:
```bash
npm start
```
4.实现效果
主要特点:
- 每个按键都有3D效果,点击时会有下沉感
- 运算符按钮使用橙色突出显示
- 数字按钮使用灰色
- 按钮悬停时有亮度变化
- 响应式布局
- 完整的计算器功能
使用方法:
1. 点击数字输入数值
2. 点击运算符进行运算
3. 点击等号得出结果
4. 点击C清除所有内容
修改样式:
- 可以在`styles.js`中调整颜色、阴影、大小等
- 3D效果主要通过`transform-style: preserve-3d`和`translateZ`实现
- 按钮的下沉效果通过`:active`状态和`transform`实现
需要注意的是,这个计算器使用了`eval()`函数来计算结果,在实际生产环境中应该使用更安全的计算方法。