iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Build on Google AI

液態玻璃美學:結合 Gemini 與 Antigravity IDE,打造 120FPS、多視窗比例與網格順延推擠佈局的強大商務運算終端系列 第 15

Day 15:【高階商務計算機】解決長算式盲打與中斷重算的焦慮——雙行即時預覽與連續運算鏈狀態機

  • 分享至 

  • xImage
  •  

昨天我們完成了強大的科學計算矩陣與智慧括號平衡,讓計算機能夠從容應對各類巢狀三角函數與對數求值。然而,在商務與日常運算的真實場景中,一個顯著的使用者體驗痛點浮現出來:當算式越來越長、括號層層嵌套時,使用者往往處於盲打狀態,非得等到按下等於按鍵的那一刻,才能知道最終答案或是否輸入有誤。

為了解決長算式輸入過程中的不確定感,並消除每次計算結束後必須手動重打上一輪答案的打斷感,今天我們實作頂部雙行即時預覽機制與連續運算鏈狀態機。在使用者鍵入算式的每一個瞬間,系統皆在無延遲背景下估算中途結果並映射至頂部副螢幕;當按下等於求得定解後,若緊接著鍵入運算子,狀態機便流暢地將現有結果轉化為下一輪運算的基底,並搭配硬體加速級的動態對調動畫,提供極致平順的連續計算體驗。


專案正式上線體驗與開源倉庫

本專案的所有功能皆已完成端到端整合,並透過 GitHub Pages 提供隨開即用的雲端線上版本。介面專為各類行動裝置、平板與桌上型高解析度螢幕完成高更新率動態適配,支援所有現代瀏覽器:


一、技術痛點:算式長度失控與計算流程中斷

傳統計算機或一般網頁計算器在互動設計上經常存在兩大操作斷層:

第一個斷層是運算過程中的資訊盲區。使用者在輸入複雜的四則運算或多層括號時,螢幕只單純顯示當前按下的字元或目前單一數值。若使用者在中途想確認當前數值累計到多少,必須先按等於打斷輸入,隨後再重新想辦法接續運算。這種資訊黑盒子在處理連乘、折讓與跨項加總時,極易讓操作者產生疑慮而反覆清零重新鍵入。

第二個斷層是等於求值後的狀態死鎖。多數初階計算機在計算出結果後,內部狀態沒有明確的轉移定義。使用者如果想將算出來的五萬三千元直接打八折,按下一乘號時,系統若非將螢幕直接清為零,就是將先前的算式字串生硬拼接,導致語法報錯。要維持商務運算的心流,計算機核心必須擁有一套明確定義的狀態機,既能在鍵入數字時俐落開創新局,又能在鍵入運算子時自然延續上回合成果。

二、架構設計:即時估算管線與連續鏈狀態機

為了解決上述體驗缺陷,我們在前端顯示管線與輸入分發器中建立了雙軌協作體系:

雙行無延遲動態預覽管線:
在螢幕結構上設計了主算式區與預覽副螢幕。當系統處於輸入狀態時,使用者輸入的長算式展示於主螢幕;與此同時,渲染管線會在每次 DOM 更新前,將當前算式透過智慧括號補齊函數封裝,送入安全求值器進行乾跑估算。若當前輸入能成功推導出有效數值,副螢幕便以略微淡化的低飽和色彩即時呈現估算結果,讓使用者在打字當下眼角餘光即可預知答案。

連續運算鏈狀態轉移核心:
系統引入精準布林標記掌控當前生命週期。當使用者按下等於鍵觸發求值完成後,標記切換為求值完成狀態。此時若使用者接著輸入數字或小數點,狀態機識別為全新算式起始,自動清空舊有歷史;若使用者按下加減乘除或百分比等運算子,狀態機則自動將上一輪算出的最終答案保留作為第一運算元,並將標記重置為輸入中狀態,達成真正的無縫連續運算鏈。

真動態對調動畫機制:
為了在按下等於的那一瞬間給予使用者清晰的視覺反饋,我們採用了原生 Web Animations API 結合 FLIP 動畫原則。在主螢幕的長算式被結果取代、副螢幕被算式定格的瞬間,計算兩者在畫面的精準像素垂直位移,讓答案有如自主螢幕躍升至定位,呈現高幀率且無重排抖動的物理反饋。

三、核心代碼精華:script.js

以下為動態即時預覽在螢幕渲染週期中的實作,以及求值觸發時的狀態機轉移與動畫銜接代碼:

// 雙行即時預覽管線:在 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 套用立體三維變形。這樣一來,整個對調過程完全由圖形處理器以專屬圖層渲染,即使在每秒一百二十次的高更新率螢幕上,亦能保有零掉幀的絲滑感。

心流不受干擾的連續鍵入設計:
狀態機在使用者點擊退格鍵或清除鍵時,同樣具備主動識別能力。若使用者在剛計算出答案的狀態下按下退格鍵,系統不會單純只刪除答案的最後一個字元,而是將整個過往計算重置為歸零初始態;而在需要連續算計折扣、加成稅額的連鎖流程中,運算子可即時鎖定前次答案,徹底消除了重複輸入前次總額的煩躁。

五、總結

今天我們成功構建了頂部雙行即時預覽與連續運算鏈狀態機,消除了長算式盲打帶來的焦慮感,並讓計算機在單次求值與連續運算之間取得完美的體驗平衡。結合動態對調動畫與安全乾跑管線,商務計算的每一步操作都具備了即時可視的流暢度。

明日預告

從明天開始,我們將正式邁入第四階段:全球即時匯率中心與智慧記帳資料庫。我們將揭開本地模式時區智慧定位的秘密,以及如何在不打擾算式輸入的前提下優雅收合頂部匯率動態膠囊。時區與網際網路通訊協定雙重自動定位機制,當來源幣別與目標幣別相同時自動啟動本地模式收合膠囊;算式未完成時保持展開防止資訊跳動。


上一篇
Day 14:【高階商務計算機】手機計算機也能跑三角函數!科學計算擴充矩陣與智慧括號平衡
系列文
液態玻璃美學:結合 Gemini 與 Antigravity IDE,打造 120FPS、多視窗比例與網格順延推擠佈局的強大商務運算終端15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言