NBlog

由一条咸鱼搭建的博客

[代码日记] 手写多功能计算器! (未完工+难题)

By NriotHrreion2023-01-18

Ferrum Explorer的开发暂时告一段落, 这下我又不知道下一个项目写什么了. 有一天, 我突然想到, 下一个项目可以是一个多功能计算器!

开启新项目

接下来就是用create-react-app搭建一个React+Typescript的新项目. 因为计算器的英文是"calculator", 钙的英文是"calcium", 它们的前四个字母相同, 所以我就将"Calcium"作为项目的名称.

项目有了, 接下来就构思整个app的功能和页面的布局.

功能

在功能方面, 我计划做三个模式: 通用模式、函数图像模式、程序员模式

布局

在布局方面, 我打算参考VSCode, 做一个像下图一样的布局.

布局设计布局设计

在最左侧的那条较窄的侧边栏中放三个模式的切换按钮, 在左侧较宽的侧边栏中放历史记录列表, 在右侧上部放输出栏, 在右侧下部放输入栏.

制作模式按钮图标

原本我是打算在Google Fonts里找图标的, 但是找了一圈发现根本就没有符合我想法的图标, 于是我就自己用矢量图软件Ai画了几个.

通用模式

通用模式的开发还是比较顺利的. 大致的开发过程是: 输入栏 > 输出栏 (包括输入框) > 算式编译执行器 > 历史记录栏

输入栏

输入栏比较简单, 它无非就是几列几排的按钮, 难点在于如何使它正确布局.

我这里使用flex布局模式. 将整个输入栏分成4大块: 一块是数学函数类, 一块是常用类, 一块是变量或常量字符类, 还有一块放版本号.

输入栏输入栏

接着, 将每一排看作一个整体, 将整个输入栏再次分为11排, 每一排再用flex将每一个按钮放好, 中间的空格用单独的<div>占位, 较大的按钮(如上图的"%"和"Result")用css中的flex-grow来控制宽度.

输入栏布局方式输入栏布局方式

输出栏

输出栏比较复杂. 我将它分为输入框与输出框两部分, 虽说是输出栏, 但是它既包括输入也包括输出. 由于原生HTML的输入框不是很友好, 因此我不得不自己做一个自制版的<input type="text">, 所以我说输出栏是比较复杂的.

输出栏的布局如图:

输出栏布局方式输出栏布局方式

输入框的每一个字符都由一个单独的<span>来包裹, 如果遇到像"sin"之类的整体, 则将这个整体直接包进<span>即可, 这样一来才便于编译执行器解析. 如图所示:

输入框布局方式输入框布局方式

输出框就比较简单了, 将结果直接输出来就可以.

算式编译执行器

这个编译执行器我觉得对我来说是最难的部分, 我将它单独存在Compiler.tsx文件中.

它大致的原理就是将输入框传来的算式(为字符串数组, 数组中每一个元素都是一个单独的符号)进行遍历, 识别每一个字符, 并将它存入编译后的数字列表与运算符号列表; 如果遇到括号, 就将整个括号内的内容单独存储, 然后运用递归来完成括号内的编译; 如果遇到函数, 就向函数列表中查找对应的函数, 然后进行计算.

编译完成后就是执行. 执行的部分按照我们小学就学过的"先乘除, 后加减"的原则, 同时遍历数字列表与运算符号列表, 然后进行计算(此时括号内的算式已被转换为算式的结果).

历史记录栏

历史记录栏是最简单的部分, 只需要监听输出框的输出, 然后将输出的内容连同输入的式子存入历史记录中并显示出来即可.

函数图像模式

函数图像模式的开发涉及到<canvas>上下文的调用与渲染, 然而这玩意性能比较差, 于是出了很多超出我现有水平的问题, 以至于我无法解决.

遇到的问题

就在我以为函数图像模式的开发如此顺利的时候, 我发现仅需要2~3个函数图像就可以让整个<canvas>的画面非常卡顿, 于是我打算通过Javascript的新特性Service Worker来解决这个问题.

但当我实装了Worker之后, 我发现它渲染出来的函数图像会不断闪烁, 并且渲染某些函数图像的时候会渲染错误, 这就太令人匪夷所思了. 最后归根到底还是我的水平太低, 以及<canvas>性能太差...

于是乎, 整个开发进度就卡在这里了...

总结

没啥好说, 项目先搁置了, 咕咕咕~

项目已经部署在: calcium.js.org