昨天我們完成了強大的科學計算矩陣與智慧括號平衡,讓計算機能夠從容應對各類巢狀三角函數與對數求值。然而,在商務與日常運算的真實場景中,一個顯著的使用者體驗痛點浮現出來:當算式越來越長、括號層層嵌套時,使用者往往處於盲打狀態,非得等到按下等於按鍵的那一刻,才能知道最終答案或是否輸入有誤。
為了解決長算式輸入過程中的不確定感,並消除每次計算結束後必須手動重打上一輪答案的打斷感,今天我們實作頂部雙行即時預覽機制與連續運算鏈狀態機。在使用者鍵入算式的每一個瞬間,系統皆在無延遲背景下估算中途結果並映射至頂部副螢幕;當按下等於求得定解後,若緊接著鍵入運算子,狀態機便流暢地將現有結果轉化為下一輪運算的基底,並搭配硬體加速級的動態對調動畫,提供極致平順的連續計算體驗。
本專案的所有功能皆已完成端到端整合,並透過 GitHub Pages 提供隨開即用的雲端線上版本。介面專為各類行動裝置、平板與桌上型高解析度螢幕完成高更新率動態適配,支援所有現代瀏覽器:
傳統計算機或一般網頁計算器在互動設計上經常存在兩大操作斷層:
第一個斷層是運算過程中的資訊盲區。使用者在輸入複雜的四則運算或多層括號時,螢幕只單純顯示當前按下的字元或目前單一數值。若使用者在中途想確認當前數值累計到多少,必須先按等於打斷輸入,隨後再重新想辦法接續運算。這種資訊黑盒子在處理連乘、折讓與跨項加總時,極易讓操作者產生疑慮而反覆清零重新鍵入。
第二個斷層是等於求值後的狀態死鎖。多數初階計算機在計算出結果後,內部狀態沒有明確的轉移定義。使用者如果想將算出來的五萬三千元直接打八折,按下一乘號時,系統若非將螢幕直接清為零,就是將先前的算式字串生硬拼接,導致語法報錯。要維持商務運算的心流,計算機核心必須擁有一套明確定義的狀態機,既能在鍵入數字時俐落開創新局,又能在鍵入運算子時自然延續上回合成果。
為了解決上述體驗缺陷,我們在前端顯示管線與輸入分發器中建立了雙軌協作體系:
雙行無延遲動態預覽管線:
在螢幕結構上設計了主算式區與預覽副螢幕。當系統處於輸入狀態時,使用者輸入的長算式展示於主螢幕;與此同時,渲染管線會在每次 DOM 更新前,將當前算式透過智慧括號補齊函數封裝,送入安全求值器進行乾跑估算。若當前輸入能成功推導出有效數值,副螢幕便以略微淡化的低飽和色彩即時呈現估算結果,讓使用者在打字當下眼角餘光即可預知答案。
連續運算鏈狀態轉移核心:
系統引入精準布林標記掌控當前生命週期。當使用者按下等於鍵觸發求值完成後,標記切換為求值完成狀態。此時若使用者接著輸入數字或小數點,狀態機識別為全新算式起始,自動清空舊有歷史;若使用者按下加減乘除或百分比等運算子,狀態機則自動將上一輪算出的最終答案保留作為第一運算元,並將標記重置為輸入中狀態,達成真正的無縫連續運算鏈。
真動態對調動畫機制:
為了在按下等於的那一瞬間給予使用者清晰的視覺反饋,我們採用了原生 Web Animations API 結合 FLIP 動畫原則。在主螢幕的長算式被結果取代、副螢幕被算式定格的瞬間,計算兩者在畫面的精準像素垂直位移,讓答案有如自主螢幕躍升至定位,呈現高幀率且無重排抖動的物理反饋。
以下為動態即時預覽在螢幕渲染週期中的實作,以及求值觸發時的狀態機轉移與動畫銜接代碼:
// 雙行即時預覽管線:在 updateDisplay 渲染階段即時估算中途答案
function updateDisplay(animType = 'none') {
if (!DOM.resultScreen) DOM.init();
const { resultScreen, formulaScreen, convertedScreen } = DOM;
if (!resultScreen) return;
let formattedInput = "";
if (isEvaluated) {
formattedInput = formatNumber(currentInput);
} else {
formattedInput = formatInputDisplay(currentInput);
}
// 處理主螢幕數值渲染、游標閃爍與進位位移動畫
if (!isEvaluated && cursorPos !== null && cursorPos >= 0 && cursorPos <= currentInput.length) {
let rawIdx = 0, fmtIdx = 0;
while (rawIdx < cursorPos && fmtIdx < formattedInput.length) {
if (formattedInput[fmtIdx] === ',') fmtIdx++;
else { rawIdx++; fmtIdx++; }
}
const before = formattedInput.slice(0, fmtIdx);
const after = formattedInput.slice(fmtIdx);
resultScreen.innerHTML = `${before}<span class="formula-cursor"></span>${after}`;
} else {
resultScreen.textContent = formattedInput;
}
// 頂部副螢幕:算式輸入階段進行無延遲背景自動補齊估算
if (!isEvaluated) {
try {
let evalVal = safeEvaluate(autoCloseParentheses(currentInput));
if (math.isBigNumber(evalVal) && evalVal.isFinite()) {
formulaScreen.textContent = "= " + formatNumber(evalVal);
} else {
formulaScreen.textContent = "";
}
} catch(e) {
formulaScreen.textContent = "";
}
}
requestBatchDisplayUpdate();
throttledExchange();
}
接下來是輸入分發器中,連續運算鏈如何依據當前狀態流暢切換:
// 連續運算鏈輸入分發器
function instantInput(e, funcName, param) {
triggerVibration(false);
if (funcName !== 'evaluateExpr') {
hideCategoryBubble();
}
requestAnimationFrame(() => {
let animType = 'none';
if (funcName === 'inputNum') {
// 若上一步剛求值完成,鍵入數字則視為開啟全新計算
if (isEvaluated) {
currentInput = param === "." ? "0." : param;
isEvaluated = false;
cursorPos = null;
} else {
// 正常輸入數字與小數點判斷
if (param === ".") {
const currentTokens = currentInput.split(/[\+\-\×\÷\%\(\)]/);
const lastToken = currentTokens[currentTokens.length - 1];
if (lastToken.includes('.')) return;
const lastChar = currentInput.slice(-1);
if (['+', '-', '×', '÷', '(', '%'].includes(lastChar) || currentInput === "") {
currentInput += "0.";
} else {
currentInput += ".";
}
} else {
if (currentInput === "0") currentInput = param;
else currentInput += param;
}
}
animType = 'append';
} else if (funcName === 'inputOperator') {
// 若上一步剛求值完成,鍵入運算子自動承接上一輪答案作為基底
isEvaluated = false;
if (currentInput.endsWith('.')) {
currentInput = currentInput.slice(0, -1);
}
const lastChar = currentInput.slice(-1);
if (['+', '-', '×', '÷'].includes(lastChar) && param !== '%') {
currentInput = currentInput.slice(0, -1) + param;
} else {
currentInput += param;
}
animType = 'append';
} else if (funcName === 'evaluateExpr') {
// 觸發等於運算:紀錄歷史、切換狀態並執行 FLIP 對調補幀
try {
currentInput = autoCloseParentheses(currentInput);
let res = safeEvaluate(currentInput);
if (!math.isBigNumber(res) || !res.isFinite()) throw new Error("Invalid output");
const resultStr = resultToInputString(res);
cursorPos = null;
const formulaScreen = document.getElementById('formula-screen');
const resultScreen = document.getElementById('result-screen');
const fRect1 = formulaScreen.getBoundingClientRect();
const rRect1 = resultScreen.getBoundingClientRect();
const oldFormulaText = currentInput;
currentInput = resultStr;
isEvaluated = true;
formulaScreen.textContent = oldFormulaText + " =";
updateDisplay();
const fRect2 = formulaScreen.getBoundingClientRect();
const rRect2 = resultScreen.getBoundingClientRect();
const fOffset = rRect1.top - fRect2.top;
const rOffset = fRect1.top - rRect2.top;
const duration = 280;
const easing = 'cubic-bezier(0.16, 1, 0.3, 1)';
formulaScreen.animate([
{ transform: `translate3d(0, ${fOffset}px, 0) scale(1.1)`, opacity: 0.9 },
{ transform: 'translate3d(0, 0, 0) scale(1)', opacity: 0.5 }
], { duration, easing, fill: 'none' });
resultScreen.animate([
{ transform: `translate3d(0, ${rOffset}px, 0) scale(0.85)`, opacity: 0.6 },
{ transform: 'translate3d(0, 0, 0) scale(1)', opacity: 1 }
], { duration, easing, fill: 'none' });
return;
} catch(err) {
const errorMsg = getDetailedErrorMsg(currentInput, err);
document.getElementById('formula-screen').textContent = errorMsg;
triggerErrorFeedback(errorMsg);
}
}
updateDisplay(animType);
});
}
在雙行預覽與運算鏈狀態機的整合中,我們針對效能與感知體驗進行了深度調校:
防報錯安全乾跑機制:
使用者在輸入算式時,常常處於未完成的合法語法狀態,例如剛按下了加號或左括號。若直接將當前字串送入求值器,會引發大量的例外捕捉開銷。因此,我們在傳入預覽求值前,先經由智慧括號自動閉合函式進行預處理;若中途算式仍無法推導出數值,則靜默清空副螢幕,絕不噴出錯誤提示或破壞輸入節奏。
無重排衝擊的硬體加速動畫:
傳統透過修改樣式表類別或操作高度邊界達成的動畫,容易引起主執行緒的重新排版與重繪。在求值切換時,我們先使用座標擷取函式記錄主螢幕與副螢幕的絕對垂直座標差,再直接調用瀏覽器底層的 Web Animations API 套用立體三維變形。這樣一來,整個對調過程完全由圖形處理器以專屬圖層渲染,即使在每秒一百二十次的高更新率螢幕上,亦能保有零掉幀的絲滑感。
心流不受干擾的連續鍵入設計:
狀態機在使用者點擊退格鍵或清除鍵時,同樣具備主動識別能力。若使用者在剛計算出答案的狀態下按下退格鍵,系統不會單純只刪除答案的最後一個字元,而是將整個過往計算重置為歸零初始態;而在需要連續算計折扣、加成稅額的連鎖流程中,運算子可即時鎖定前次答案,徹底消除了重複輸入前次總額的煩躁。
今天我們成功構建了頂部雙行即時預覽與連續運算鏈狀態機,消除了長算式盲打帶來的焦慮感,並讓計算機在單次求值與連續運算之間取得完美的體驗平衡。結合動態對調動畫與安全乾跑管線,商務計算的每一步操作都具備了即時可視的流暢度。
從明天開始,我們將正式邁入第四階段:全球即時匯率中心與智慧記帳資料庫。我們將揭開本地模式時區智慧定位的秘密,以及如何在不打擾算式輸入的前提下優雅收合頂部匯率動態膠囊。時區與網際網路通訊協定雙重自動定位機制,當來源幣別與目標幣別相同時自動啟動本地模式收合膠囊;算式未完成時保持展開防止資訊跳動。