从这篇文章开始,将会花几篇文章的篇幅来介绍如何构建一个在线的计算器,将前面学习的一些知识串在一起,通过实战加强对理论知识的理解。实现计算器主要分为两个部分,一个是构建计算器的UI,另一个通过JavaScript实现计算器的功能。在完成这两个部分内容的学习,建议您需要对下面的几个知识点有所了解:
这两个部分可以帮助你快速完成计算器的UI效果。
if/else
语句for
循环- JavaScript函数
- ES6的箭头函数
&&
和||
操作符- 如何使用
textContent
属性来更改文本 - 如何使用事件委托模式来添加事件侦听器
这篇文章的目的是通过构建一个简单的计算器来帮助大家如何把学到的知识点串起来,能学习致用。
构建计算器的UI
在这一节中主要向大家演示如何使用CSS网格和渐变这样的属性构建一个简单的计算器UI,其中网格主要用来实现计算器的布局,然后使用渐变来构建计算器每个按键的效果。在开始阅读下面的内容之前,你最好对网格和渐变有一定的了解,因为我们在接下来的内容并不会花时间来阐述网格和渐变。只会借助这两个CSS属性来帮助大家构建计算器UI。
注意,整个制作计算器用到的实例,都将在CodePen上完成。
上图是Mac电脑上的一个计算器,我们来做一个类似的,只不过功能可能会更为简单一点。而我们要实现的计算器UI像下面这样:
HTML结构
整个计算器我们分为三个部分,左上角控制窗口的按钮calculator-bar
、计算器计算值显示区域calculator-display
和计算器按键calculator-keys
:
首先我们需要一个实现计算器的HTML结构:
<div class="calculator">
<div class="calculator-bar">
<div class="close"></div>
<div class="minimize"></div>
<div class="maximize"></div>
</div>
<div class="calculator-display"></div>
<div class="calculator-keys">
<button type="button" class="operator" value="+">+</button>
<button type="button" class="operator" value="-">-</button>
<button type="button" class="operator" value="*">×</button>
<button type="button" class="operator" value="/">÷</button>
<button type="button" value="7">7</button>
<button type="button" value="8">8</button>
<button type="button" value="9">9</button>
<button type="button" value="4">4</button>
<button type="button" value="5">5</button>
<button type="button" value="6">6</button>
<button type="button" value="1">1</button>
<button type="button" value="2">2</button>
<button type="button" value="3">3</button>
<button type="button" value="0">0</button>
<button type="button" class="decimal" value=".">.</button>
<button type="button" class="all-clear" value="all-clear">AC</button>
<button type="button" class="equal-sign" value="=">=</button>
</div>
</div>
设计计算器UI风格
为了更好的设计计算器UI风格,先添加一点点基本样式。比如说,重置各元素的margin
、padding
和box-sizing
:
html {
font-size: 62.5%;
box-sizing: border-box;
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: inherit
}
使用渐变、混合模式和background-size
模拟给计算器做一个桌面背景的效果:
body {
background:
radial-gradient(50% 100%, rgba(255,255,255,.2), rgba(255,255,255,0)),
linear-gradient(90deg, rgba(50,100,170,.7) 1px, transparent 0),
linear-gradient(180deg, rgba(50,100,170,.7) 1px, transparent 0),
linear-gradient(90deg, rgba(50,100,170,.4) 1px, transparent 0),
linear-gradient(180deg, rgba(50,100,170,.4) 1px, transparent 0),
linear-gradient(90deg, rgba(50,100,170,1) 2px, transparent 0),
linear-gradient(180deg, rgba(50,100,170,1) 2px, transparent 0),
url(//images.pexels.com/photos/1540258/pexels-photo-1540258.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260) no-repeat;
background-color: #074b97;
background-size:100% 100%, 50px 50px,50px 50px,25px 25px,25px 25px,100px 100px, 100px 100px, cover;
background-attachment: local;
background-blend-mode: lighten;
}
上面代码最关键的部分,使用CSS的Gradient、多背景和background-size
构建了背景图纹,再配上CSS的图层模式,得到上图的效果。
在body
使用display:flex
让整个计算器水平居中显示:
body {
// 同前面的代码 ...
width: 100vw;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
在CSS中实现水平居中有很多种方法,这里用了其中最简单的方法而以。
接着使用绘制计算器窗口的效果,模拟iOS系统的窗口效果:
.calculator {
background: #1d1e22;
padding:2.8rem .64rem .64rem;
color: white;
border-radius: .5rem;
box-shadow: 0 .3rem 3rem .1rem rgba(0,0,0,0.6);
position: relative;
min-width: 40rem;
}
再配上左上角的,关闭、最小化和最大化三个按钮:
.calculator-bar {
display: grid;
grid-template-columns: repeat(3, 1.4rem);
grid-column-gap: .6rem;
position: absolute;
top: .64rem;
left: .64rem;
> div {
cursor: pointer;
height: 1.4rem;
width: 1.4rem;
border-radius: 100%;
position: relative;
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
color: #1d1e22;
opacity: 0;
transition: opacity .2s linear;
}
}
&:hover div::before,
&:hover div::after {
transition: opacity .2s linear;
opacity: 1;
}
.close {
background: rgb(255,90,90);
&::before {
transform: translate(-50%, -50%) rotate(-45deg);
width: 1rem;
height: 1px;
background: currentColor;
}
&::after {
transform: translate(-50%, -50%) rotate(45deg);
width: 1rem;
height: 1px;
background: currentColor;
}
}
.minimize {
background: rgb(90,220,90);
&::before {
transform: translate(-50%, -50%);
width: 1rem;
height: 1px;
background: currentColor;
}
}
.maximize {
background: rgb(255,200,90);
&::before {
border: 5px solid transparent;
border-color: transparent transparent transparent currentColor;
transform: translate(0%, -90%) rotate(-45deg);
}
&::after {
border: 5px solid transparent;
border-color: transparent currentColor transparent transparent;
transform: translate(-90%, 0%) rotate(-45deg);
}
}
}
来看几处关键性的代码,在.calculator-bar
使用网格布局,对三个按钮进行布局:
.calculator-bar {
display: grid;
grid-template-columns: repeat(3, 1.4rem);
grid-column-gap: .6rem;
//...
}
如果你使用Firefox浏览器,并开启网格检查器功能,你就能看到网格的一些参数和每个网格项所在的位置,比如下图所示:
有关于如何使用Firefox浏览器网格检查器调试CSS网格布局相关的详细介绍,可以阅读《使用Firefox 网格检查器调试 CSS网格布局》一文。
另外为了更轻易的控制按钮上的图标颜色,采用了currentColor
来控制background-color
和border-color
。这样的好处是可以根据color
就自动的调整颜色。同时使用transition
添加了一点点过渡效果,因此当你的鼠标悬浮到左上角的按钮上时,对应的Icon出现会有一个过渡的小动效:
接着开始构建计算器的面板了,先来给显示屏(显示数字的)添加样式,并且其默认开启的值为0
:
<div class="calculator-display">0</div>
加上下面的样式代码:
.calculator-display {
font-size: 5rem;
height: 80px;
padding: 0 20px;
background-color: #1d1e22;
color: #fff;
display: flex;
align-items: center;
justify-content: flex-end;
}
此时看到的效果如下:
现在就差给按键添加样式了。我们给button
添加样式:
button {
height: 60px;
border-radius: 3px;
border: 1px solid #c4c4c4;
font-size: 2rem;
color: #333;
background-color: #fff;
background-image: linear-gradient(to bottom,transparent,transparent 50%,rgba(0,0,0,.04));
box-shadow:
inset 0 0 0 1px rgba(255,255,255,.05),
inset 0 1px 0 0 rgba(255,255,255,.45),
inset 0 -1px 0 0 rgba(255,255,255,.15),
0 1px 0 0 rgba(255,255,255,.15);
text-shadow: 0 1px rgba(255,255,255,.4);
&:hover {
background-color: #eaeaea;
}
&.operator {
background-color: #eee;
color: #337cac;
}
&.all-clear {
background-color: #f0595f;
border-color: #b0353a;
color: #fff;
&:hover {
background-color: #f17377;
}
}
&.equal-sign {
background-color: #2e86c0;
border-color: #337cac;
color: #fff;
&:hover {
background-color: #4e9ed4;
}
}
}
按钮效果有了,接下来是按键排列了。布局有多种方式,在这里我们主要是想来练习CSS的网格布局:
.calculator-keys {
display: grid;
}
CSS网格布局和Flexbox类似,一旦显式设置了display
的值为grid
的时候,那么该元素就变成了网格容器(Grid container),其子元素就变成了网格项(Grid items)。比如在我们这个示例中,.calculator-keys
就成了网格容器,button
就成了网格项。这个时候使用Firefox浏览器查看的时候,就如下图这样:
现在的效果是,每一个按键占据了一行,离我们想要的效果还是有一定的差异。我们需要改变行和列,在网格布局中,可以通过grid-template-rows
和grid-template-columns
来设置:
.calculator-keys {
grid-template-columns: 60px 60px 60px 60px;
}
现在每一列都是固定宽度,如果每列要均分容器的宽度,按照以前的方式的话,我们需要去做相应的计算,相对而言是要更蛋疼一点的。比如:
.calculator-keys {
grid-template-columns: 25% 25% 25% 25%;
}
这个示例是分为四列,所以很好计算,如果要是复杂点,人肉计算出来的值总是有点偏差,这个时候就需要借用客户端(比如浏览器)来帮我们做这个计算了。在CSS中可以使用calc()
函数帮我们计算。不管是使用固定单位,还是百分比,在我们这个示例中都是均分的四列,同样的值写四次有点惹人厌。其实在网格布局中还有一个特性,那就是repeat()
函数,可以把事情变得简单的多。repeat()
函数允许我们给网格多个列指定相同的值。它也接受两个值:重复的次数和重复的值:
.calculator-keys {
grid-template-columns: repeat(4, calc(100% / 4));
}
repeat()
让我们省事多了,而calc()
也能帮我们省不少的事,假设我们按键之间有20px
的间距(列与列之间的距离),那么calc()
的魅力在此处就更为突出了:
// (100% - 2rem * 3) / 4 => 平分剩下的宽度
.calculator-keys {
grid-template-columns: repeat(4, calc((100% - 2rem * 3) / 4));
grid-gap: 2rem;
}
上面的代码中还添加了grid-gap
,用来设置网格行与行,列与列之间的间距。其实grid-gap
是grid-column-gap
和grid-row-gap
的简写方式。运用上面的代码之后,得到的效果将会如下图所示:
虽然calc()
很强大,但有时候一不小心,就会整出问题,但在网格布局中,有一个fr
度量单位。fr
是一个很有意思的单位,它的用法和CSS的其他长度单位(比如%
、px
、em
、rem
和vw
等)是一样的。用这个神奇的单位重写一下代码:
.calculator-keys {
grid-template-columns: repeat(4, 1fr);
grid-gap: 2rem;
}
得到的效果和上图是一样的。这里的repeat(4, 1fr)
其实表达的意思就是,把容器剩余的宽度平均分成四等份。
为了让按键和网格容器的边缘也有类似网格间距,可以在.calculator-keys
上添加一个2rem
的padding
,同时给容器添加一个背景颜色:
.calculator-keys {
grid-template-columns: repeat(4, 1fr);
grid-gap: 2rem;
padding: 2rem 1.36rem;
background-color: #fff;
border-radius: 0 0 .5rem .5rem;
}
这时效果看上去就要舒服多了:
现在效果离我们越来越近了,有些按键还需要继续调整。在网格布局中有几种方法可以将元素放在网格的特定位置。比如最简单的网格线方式。网格线是列和行之间的分界线。使用网格调试器可以看得特清楚。
正如上图所示,你看到我们列的编号从1
到5
,行的编号从1
到6
。在网格中,网格线分为两种,一种是显式的网格线,另一种是隐式的网格线,比如上图中列的网格线-1
到-5
就是隐式网格线。在网格调试器中,可以非常轻易的看到每个网格所在位置的网格线:
比如等号按键,需要从行5/列1
移动行2/列1
,这里的行5
,列1
其实指的也就是网格线。在网格布局中,咱们可以通过grid-row
和grid-column
来指定行和列的网格线。其中grid-row
是grid-row-start
和grid-row-end
的简写属性,而grid-column
是grid-column-start
和grid-column-end
的简写。
.equal-sign {
grid-row-start: 2;
grid-column-start: 1;
grid-row-end: 3;
grid-column-end: 2;
}
也可以使用简写的属性:
.equal-sign {
grid-row: 2 / 3;
grid-column: 1 / 2;
}
这样等号按键就移到了我们想要的位置了:
还有一种更为简的方式,使用网格区域grid-area
也可以给元素指定到特定的位置,比如上面的代码我们可以改用grid-area
:
.equal-sign {
// grid-row-start / grid-column-start / grid-row-end / grid-column-end
grid-area: 2 / 1 / 3/ 2;
}
等号移到了指定的位置,现在需要让等号按钮跨四行:
使用grid-area
就可以做到:
.equal-sign {
grid-area: 2 / 1 / 6/ 2;
}
上面是通过grid-area
让网格跨行,其实在网格布局中还有一种更简单的方式,使用span
关键词来实现单元格合并。比如像下面这样改造上面的代码,以达到相同的效果:
.equal-sign {
grid-row: 2 / span 5;
}
等号按键应该是在最右侧,所以咱们还需要再做进一步改造,改变等号列的位置:
.equal-sign {
grid-row: 2 / span 4;
grid-column: 4 / 5;
height: 100%;
}
最终的效果如下:
是不是有点小成就感了。
小结
到这里为止,我们看到的计算器其实只是一个静态的计算器,没有任何功能,不能帮你做任何计算。因为上面的内容,我们只是通过CSS的一些知识帮助我们完成了UI的效果。实现上面的计算器效果,我们还是运用了不少的CSS知识点。比如渐变、混合模式、多背景和背景尺寸结合绘制桌面效果,其次使用网格来布局,轻易的控制每一个按键到指定位置;另外还有一些小技巧,比如calc()
函数让浏览器帮你计算,currentColor
来控制颜色的优势之处。
当然要实现这样的一个UI效果还有很多种方法,如果你感兴趣的话,自己可以动手一试。接下来,我们将通过JavaScript给计算器赋予计算的能力。感兴趣的同学欢迎关注后续的相关更新。