BMI 計算器技術實作

深入了解 BMI 計算器的實作原理,包含公制英制雙系統、即時計算引擎、視覺化圖表和健康建議系統

概述

BMI 計算器是一個功能強大的線上健康評估工具,專為關注健康的用戶設計,能夠計算身體質量指數(BMI)、提供健康評估、顯示理想體重範圍。 本工具採用純前端架構,完全在瀏覽器端運行,零網路傳輸,確保數據的絕對隱私和安全性。 提供公制/英制雙系統切換、即時計算、視覺化圖表、個性化健康建議等專業功能。

🔒 隱私保護承諾

所有 BMI 計算和健康數據處理都在您的瀏覽器本地進行,數據不會上傳到任何伺服器,確保您的健康資料完全安全。

技術架構與核心設計

整體架構設計

技術項目 實作方式 設計考量
前端框架 純 JavaScript (ES6+) 零依賴,最小化載入時間
計算引擎 即時計算 + 雙單位系統 精確度與靈活性並重
數據視覺化 SVG 動態圖表 + CSS 動畫 輕量級且高效能
健康評估系統 WHO 標準 + 多級別分類 國際標準與準確性
用戶介面 響應式設計 + 雙語系統 跨設備兼容性與國際化

核心類別架構

class BmiCalculator {
    constructor() {
        this.currentLanguage = 'zh';
        this.currentUnit = 'metric';
        this.currentGender = 'male';
        this.bmiValue = null;
        this.init();
    }
    
    init() {
        this.setupGlobalLanguageListener();
        this.initElements();
        this.setupEventListeners();
        this.updateLanguage();
    }
    
    // 主要計算流程
    calculateBMI(height, weight) {
        // 基於單位系統進行 BMI 計算
        // 返回 BMI 值和健康評估
    }
}

關鍵功能實現

  • 🔄 雙單位系統:支援公制(cm/kg)和英制(in/lb)即時切換,數值自動轉換
  • 📊 即時計算引擎:輸入即計算,提供實時的 BMI 值和健康評估
  • 📈 視覺化圖表:動態 SVG 圖表清楚顯示 BMI 位置和健康範圍
  • 🎯 精準分級系統:基於 WHO 標準的 6 級健康評估系統
  • 💡 個性化建議:根據 BMI 結果提供針對性的健康建議
  • ⚖️ 理想體重範圍:自動計算並顯示個人理想體重範圍
  • 🌐 完整雙語支援:中英文介面無縫切換,包含所有提示和建議
  • 📱 響應式設計:完美適配手機、平板、桌面各種設備

⚠️ 醫療聲明

BMI 只是健康評估的參考指標之一,不能作為醫療診斷的唯一依據。如有健康疑慮,請諮詢專業醫療人員。

完整程式碼實作

以下是 BMI 計算器的完整程式碼實作,按功能模組分類展示。點擊卡片標題可展開查看詳細程式碼:

📦 核心 BMI 計算器類別

功能:主要的 BmiCalculator 類別,包含建構函數、初始化方法和核心配置

/**
 * BMI Calculator - Core Implementation
 * BMI 計算器 - 核心實作
 * 
 * A comprehensive health assessment tool for calculating and analyzing BMI
 * 一個用於計算和分析 BMI 的綜合健康評估工具
 */
class BmiCalculator {
    constructor() {
        // 設定預設語言 / Set default language
        this.currentLanguage = 'zh';
        
        // 設定預設單位系統 / Set default unit system
        this.currentUnit = 'metric'; // 'metric' or 'imperial'
        
        // 設定預設性別 / Set default gender
        this.currentGender = 'male'; // 'male' or 'female'
        
        // BMI 相關數據 / BMI related data
        this.bmiValue = null;
        this.bmiCategory = null;
        this.idealWeightRange = null;
        
        // 驗證狀態 / Validation states
        this.heightValid = false;
        this.weightValid = false;
        
        // BMI 分類標準 (WHO) / BMI Classification (WHO)
        this.bmiCategories = {
            underweight: { max: 18.5, color: '#3b82f6' },
            normal: { min: 18.5, max: 24.9, color: '#10b981' },
            overweight: { min: 25, max: 29.9, color: '#f59e0b' },
            obese1: { min: 30, max: 34.9, color: '#ef4444' },
            obese2: { min: 35, max: 39.9, color: '#dc2626' },
            obese3: { min: 40, color: '#991b1b' }
        };
        
        // 單位範圍限制 / Unit range limits
        this.limits = {
            metric: {
                height: { min: 50, max: 250 }, // cm
                weight: { min: 10, max: 300 }  // kg
            },
            imperial: {
                height: { min: 20, max: 98 },  // inches
                weight: { min: 22, max: 661 }  // pounds
            }
        };
        
        // DOM 元素快取 / DOM element cache
        this.elements = {};
        
        // 啟動初始化 / Initialize
        this.init();
    }
    
    /**
     * 初始化方法 - 按順序執行所有初始化步驟
     * Initialization method - Execute all initialization steps in order
     */
    init() {
        this.setupGlobalLanguageListener(); // 必須最先執行 / Must execute first
        this.initTranslations();           // 初始化翻譯系統 / Initialize translation system
        this.initElements();               // 初始化DOM元素 / Initialize DOM elements
        this.setupEventListeners();        // 設置事件監聽 / Set up event listeners
        this.setupKeyboardShortcuts();     // 設置快捷鍵 / Set up keyboard shortcuts
        this.updateLanguage();             // 設定初始語言 / Set initial language
        this.showInitialState();           // 顯示初始狀態 / Show initial state
    }
    
    /**
     * 顯示初始狀態 - 清空所有輸入和結果
     * Show initial state - Clear all inputs and results
     */
    showInitialState() {
        this.elements.heightInput.value = '';
        this.elements.weightInput.value = '';
        this.elements.result.style.display = 'none';
        this.elements.calculateBtn.disabled = true;
        this.bmiValue = null;
        this.bmiCategory = null;
    }
}

🌐 全域語言系統整合

功能:整合 Tool Master 全域語言切換系統,支援即時雙語切換

/**
 * 初始化翻譯系統
 * Initialize translation system
 */
initTranslations() {
    // 完整的雙語翻譯字典 / Complete bilingual translation dictionary
    this.translations = {
        zh: {
            title: 'BMI 計算器',
            subtitle: '身體質量指數計算器',
            metric: '公制 (cm/kg)',
            imperial: '英制 (in/lb)',
            male: '男性',
            female: '女性',
            height: '身高',
            weight: '體重',
            calculate: '開始計算',
            categories: {
                underweight: '過輕',
                normal: '正常',
                overweight: '過重',
                obese1: '輕度肥胖',
                obese2: '中度肥胖',
                obese3: '重度肥胖'
            },
            advice: {
                underweight: '您的體重偏輕,建議多補充營養,適當增加體重。',
                normal: '恭喜!您的體重在健康範圍內,請保持良好的生活習慣。',
                overweight: '您的體重偏重,建議適當運動並注意飲食控制。',
                obese1: '您已達到輕度肥胖,建議諮詢醫生並制定減重計畫。',
                obese2: '您已達到中度肥胖,強烈建議尋求專業醫療協助。',
                obese3: '您已達到重度肥胖,請立即尋求專業醫療協助。'
            },
            ideal_weight: '建議體重範圍',
            errors: {
                height_required: '請輸入身高',
                height_range: '身高必須在合理範圍內',
                weight_required: '請輸入體重',
                weight_range: '體重必須在合理範圍內',
                calculation_failed: '計算失敗,請稍後再試',
                network_error: '網路錯誤,請稍後再試'
            }
        },
        en: {
            title: 'BMI Calculator',
            subtitle: 'Body Mass Index Calculator',
            metric: 'Metric (cm/kg)',
            imperial: 'Imperial (in/lb)',
            male: 'Male',
            female: 'Female',
            height: 'Height',
            weight: 'Weight',
            calculate: 'Calculate',
            categories: {
                underweight: 'Underweight',
                normal: 'Normal',
                overweight: 'Overweight',
                obese1: 'Obese Class I',
                obese2: 'Obese Class II',
                obese3: 'Obese Class III'
            },
            advice: {
                underweight: 'You are underweight. Consider increasing nutrient intake and gaining healthy weight.',
                normal: 'Congratulations! Your weight is in the healthy range. Maintain your good lifestyle.',
                overweight: 'You are overweight. Consider exercising regularly and controlling your diet.',
                obese1: 'You are in Class I obesity. Consider consulting a doctor for a weight loss plan.',
                obese2: 'You are in Class II obesity. Strongly recommend seeking professional medical help.',
                obese3: 'You are in Class III obesity. Please seek immediate professional medical assistance.'
            },
            ideal_weight: 'Recommended Weight Range',
            errors: {
                height_required: 'Please enter height',
                height_range: 'Height must be in a reasonable range',
                weight_required: 'Please enter weight',
                weight_range: 'Weight must be in a reasonable range',
                calculation_failed: 'Calculation failed, please try again later',
                network_error: 'Network error, please try again later'
            }
        }
    };
}

/**
 * 設置全域語言監聽器
 * Setup global language listener
 * 
 * 這是工具與 Tool Master 語言系統整合的關鍵方法
 * This is the key method for tool integration with Tool Master language system
 */
setupGlobalLanguageListener() {
    // 監聽全域語言切換事件 / Listen to global language change events
    window.addEventListener('languageChanged', (event) => {
        this.currentLanguage = event.detail.language;
        this.updateLanguage();
    });
    
    // 處理全域語言系統加載時序問題 / Handle global language system loading timing
    const checkGlobalLanguage = () => {
        if (window.globalI18n) {
            this.currentLanguage = window.globalI18n.currentLanguage;
            this.updateLanguage();
            return true;
        }
        return false;
    };
    
    // 如果全域語言系統未載入,延遲重試 / Retry if global language system not loaded
    if (!checkGlobalLanguage()) {
        setTimeout(() => {
            checkGlobalLanguage();
        }, 100);
    }
}

/**
 * 更新介面語言
 * Update interface language
 */
updateLanguage() {
    const lang = this.translations[this.currentLanguage] || this.translations.zh;
    
    // 更新所有包含 data-lang-key 的元素 / Update all elements with data-lang-key
    document.querySelectorAll('[data-lang-key]').forEach(element => {
        const key = element.getAttribute('data-lang-key');
        if (lang[key]) {
            element.textContent = lang[key];
        }
    });
    
    // 更新標題和副標題 / Update title and subtitle
    if (this.elements.title) {
        this.elements.title.textContent = lang.title;
    }
    if (this.elements.subtitle) {
        this.elements.subtitle.textContent = lang.subtitle;
    }
    
    // 更新輸入提示文字 / Update input placeholders
    if (this.elements.heightInput) {
        this.elements.heightInput.placeholder = lang.height;
    }
    if (this.elements.weightInput) {
        this.elements.weightInput.placeholder = lang.weight;
    }
    
    // 更新按鈕文字 / Update button text
    if (this.elements.calculateBtn) {
        this.elements.calculateBtn.textContent = lang.calculate;
    }
    
    // 如果有結果顯示,更新結果語言 / Update result language if displayed
    if (this.bmiValue && this.elements.result.style.display !== 'none') {
        this.displayResult();
    }
    
    // 更新 HTML lang 屬性 / Update HTML lang attribute
    document.documentElement.lang = this.currentLanguage === 'zh' ? 'zh-TW' : 'en';
}

🎯 DOM 初始化與元素管理

功能:初始化所有 DOM 元素的引用,設置初始狀態和事件監聽器

/**
 * 初始化 DOM 元素
 * Initialize DOM elements
 */
initElements() {
    // 標題元素 / Title elements
    this.elements.title = document.getElementById('title');
    this.elements.subtitle = document.getElementById('subtitle');
    
    // 單位切換按鈕 / Unit toggle buttons
    this.elements.metricBtn = document.querySelector('[data-unit="metric"]');
    this.elements.imperialBtn = document.querySelector('[data-unit="imperial"]');
    
    // 性別選擇按鈕 / Gender selection buttons
    this.elements.maleBtn = document.querySelector('[data-gender="male"]');
    this.elements.femaleBtn = document.querySelector('[data-gender="female"]');
    
    // 輸入元素 / Input elements
    this.elements.heightInput = document.getElementById('height');
    this.elements.weightInput = document.getElementById('weight');
    this.elements.heightUnit = document.getElementById('height-unit');
    this.elements.weightUnit = document.getElementById('weight-unit');
    
    // 計算按鈕 / Calculate button
    this.elements.calculateBtn = document.getElementById('calculate-btn');
    
    // 結果顯示區域 / Result display area
    this.elements.result = document.getElementById('result');
    this.elements.bmiValue = document.getElementById('bmi-value');
    this.elements.bmiCategory = document.getElementById('bmi-category');
    this.elements.bmiAdvice = document.getElementById('bmi-advice');
    this.elements.idealWeight = document.getElementById('ideal-weight');
    
    // 圖表元素 / Chart elements
    this.elements.bmiBar = document.getElementById('bmi-bar');
    this.elements.bmiIndicator = document.getElementById('bmi-indicator');
    
    // 錯誤顯示元素 / Error display elements
    this.elements.heightError = document.querySelector('.height-error');
    this.elements.weightError = document.querySelector('.weight-error');
    
    // 驗證所有必要元素是否存在 / Verify all required elements exist
    this.validateElements();
}

/**
 * 驗證所有必要的 DOM 元素
 * Validate all required DOM elements
 */
validateElements() {
    const requiredElements = [
        'heightInput', 'weightInput', 'calculateBtn',
        'result', 'bmiValue', 'bmiCategory'
    ];
    
    const missingElements = [];
    
    requiredElements.forEach(elementKey => {
        if (!this.elements[elementKey]) {
            missingElements.push(elementKey);
            console.error(`Required element missing: ${elementKey}`);
        }
    });
    
    if (missingElements.length > 0) {
        console.error('BMI Calculator initialization failed: Missing elements', missingElements);
        // 可以顯示錯誤提示給用戶 / Can show error message to user
        this.showInitializationError();
    }
}

/**
 * 設置事件監聽器
 * Setup event listeners
 */
setupEventListeners() {
    // 單位切換事件 / Unit toggle events
    this.elements.metricBtn?.addEventListener('click', () => {
        this.switchUnit('metric');
    });
    
    this.elements.imperialBtn?.addEventListener('click', () => {
        this.switchUnit('imperial');
    });
    
    // 性別選擇事件 / Gender selection events
    this.elements.maleBtn?.addEventListener('click', () => {
        this.selectGender('male');
    });
    
    this.elements.femaleBtn?.addEventListener('click', () => {
        this.selectGender('female');
    });
    
    // 輸入驗證事件 / Input validation events
    this.elements.heightInput?.addEventListener('input', () => {
        this.validateHeight();
        this.checkCalculateButton();
    });
    
    this.elements.weightInput?.addEventListener('input', () => {
        this.validateWeight();
        this.checkCalculateButton();
    });
    
    // 輸入框失焦事件 / Input blur events
    this.elements.heightInput?.addEventListener('blur', () => {
        this.formatInput('height');
    });
    
    this.elements.weightInput?.addEventListener('blur', () => {
        this.formatInput('weight');
    });
    
    // 計算按鈕事件 / Calculate button event
    this.elements.calculateBtn?.addEventListener('click', () => {
        this.handleCalculate();
    });
    
    // 防止表單提交 / Prevent form submission
    const form = document.querySelector('form');
    if (form) {
        form.addEventListener('submit', (e) => {
            e.preventDefault();
            this.handleCalculate();
        });
    }
}

/**
 * 設置鍵盤快捷鍵
 * Setup keyboard shortcuts
 */
setupKeyboardShortcuts() {
    document.addEventListener('keydown', (e) => {
        // Enter 鍵計算 / Enter key to calculate
        if (e.key === 'Enter' && !this.elements.calculateBtn.disabled) {
            this.handleCalculate();
        }
        
        // Escape 鍵清空 / Escape key to clear
        if (e.key === 'Escape') {
            this.clearAll();
        }
        
        // Tab 鍵在輸入框之間切換 / Tab key to switch between inputs
        if (e.key === 'Tab') {
            // 瀏覽器默認行為已處理 / Browser default behavior handles this
        }
    });
}

/**
 * 選擇性別
 * Select gender
 */
selectGender(gender) {
    this.currentGender = gender;
    
    // 更新按鈕狀態 / Update button states
    if (gender === 'male') {
        this.elements.maleBtn.classList.add('active');
        this.elements.femaleBtn.classList.remove('active');
    } else {
        this.elements.femaleBtn.classList.add('active');
        this.elements.maleBtn.classList.remove('active');
    }
    
    // 如果已有計算結果,重新計算 / Recalculate if result exists
    if (this.bmiValue) {
        this.handleCalculate();
    }
}

/**
 * 檢查是否可以啟用計算按鈕
 * Check if calculate button should be enabled
 */
checkCalculateButton() {
    const canCalculate = this.heightValid && this.weightValid;
    this.elements.calculateBtn.disabled = !canCalculate;
    
    if (canCalculate) {
        this.elements.calculateBtn.classList.add('enabled');
    } else {
        this.elements.calculateBtn.classList.remove('enabled');
    }
}

⚙️ BMI 計算引擎與健康評估

功能:核心 BMI 計算邏輯、單位轉換、健康分類評估和理想體重計算

/**
 * 處理計算按鈕點擊
 * Handle calculate button click
 */
handleCalculate() {
    const height = parseFloat(this.elements.heightInput.value);
    const weight = parseFloat(this.elements.weightInput.value);
    
    // 最終驗證 / Final validation
    if (!this.validateInputs(height, weight)) {
        return;
    }
    
    // 計算 BMI / Calculate BMI
    this.calculateBMI(height, weight);
    
    // 顯示結果 / Display results
    this.displayResult();
    
    // 添加計算動畫 / Add calculation animation
    this.animateResult();
}

/**
 * 計算 BMI 值
 * Calculate BMI value
 * 
 * BMI = weight(kg) / height(m)²
 * 對於英制單位需要先轉換
 * For imperial units, conversion is needed first
 */
calculateBMI(height, weight) {
    let heightInMeters, weightInKg;
    
    if (this.currentUnit === 'metric') {
        // 公制:身高(cm)轉換為米 / Metric: convert height(cm) to meters
        heightInMeters = height / 100;
        weightInKg = weight;
    } else {
        // 英制:身高(inches)和體重(pounds)轉換 / Imperial: convert height(inches) and weight(pounds)
        heightInMeters = (height * 0.0254);
        weightInKg = weight * 0.453592;
    }
    
    // 計算 BMI / Calculate BMI
    this.bmiValue = weightInKg / (heightInMeters * heightInMeters);
    
    // 保留一位小數 / Keep one decimal place
    this.bmiValue = Math.round(this.bmiValue * 10) / 10;
    
    // 確定 BMI 分類 / Determine BMI category
    this.bmiCategory = this.getBMICategory(this.bmiValue);
    
    // 計算理想體重範圍 / Calculate ideal weight range
    this.calculateIdealWeight(height);
    
    // 記錄計算歷史(可選功能)/ Record calculation history (optional feature)
    this.addToHistory({
        date: new Date(),
        height: height,
        weight: weight,
        unit: this.currentUnit,
        bmi: this.bmiValue,
        category: this.bmiCategory
    });
}

/**
 * 根據 BMI 值獲取健康分類
 * Get health category based on BMI value
 * 
 * 基於 WHO 標準分類
 * Based on WHO classification standards
 */
getBMICategory(bmi) {
    if (bmi < 18.5) {
        return 'underweight';
    } else if (bmi >= 18.5 && bmi < 25) {
        return 'normal';
    } else if (bmi >= 25 && bmi < 30) {
        return 'overweight';
    } else if (bmi >= 30 && bmi < 35) {
        return 'obese1';
    } else if (bmi >= 35 && bmi < 40) {
        return 'obese2';
    } else {
        return 'obese3';
    }
}

/**
 * 計算理想體重範圍
 * Calculate ideal weight range
 * 
 * 基於 BMI 18.5-24.9 的健康範圍
 * Based on healthy BMI range of 18.5-24.9
 */
calculateIdealWeight(height) {
    let heightInMeters;
    
    if (this.currentUnit === 'metric') {
        heightInMeters = height / 100;
    } else {
        heightInMeters = height * 0.0254;
    }
    
    // 計算理想體重範圍(BMI 18.5-24.9)/ Calculate ideal weight range (BMI 18.5-24.9)
    const minIdealWeight = 18.5 * heightInMeters * heightInMeters;
    const maxIdealWeight = 24.9 * heightInMeters * heightInMeters;
    
    if (this.currentUnit === 'imperial') {
        // 轉換回英磅 / Convert back to pounds
        this.idealWeightRange = {
            min: Math.round(minIdealWeight * 2.20462),
            max: Math.round(maxIdealWeight * 2.20462),
            unit: 'lb'
        };
    } else {
        // 公斤 / Kilograms
        this.idealWeightRange = {
            min: Math.round(minIdealWeight),
            max: Math.round(maxIdealWeight),
            unit: 'kg'
        };
    }
}

/**
 * 單位系統切換
 * Switch unit system
 */
switchUnit(unit) {
    if (this.currentUnit === unit) return;
    
    const heightInput = this.elements.heightInput;
    const weightInput = this.elements.weightInput;
    const heightValue = parseFloat(heightInput.value);
    const weightValue = parseFloat(weightInput.value);
    
    if (unit === 'imperial') {
        // 公制轉英制 / Metric to Imperial
        if (heightValue) {
            heightInput.value = (heightValue / 2.54).toFixed(1);
        }
        if (weightValue) {
            weightInput.value = (weightValue * 2.20462).toFixed(1);
        }
        this.elements.heightUnit.textContent = 'in';
        this.elements.weightUnit.textContent = 'lb';
        
        // 更新輸入範圍 / Update input ranges
        heightInput.min = this.limits.imperial.height.min;
        heightInput.max = this.limits.imperial.height.max;
        weightInput.min = this.limits.imperial.weight.min;
        weightInput.max = this.limits.imperial.weight.max;
    } else {
        // 英制轉公制 / Imperial to Metric
        if (heightValue) {
            heightInput.value = (heightValue * 2.54).toFixed(1);
        }
        if (weightValue) {
            weightInput.value = (weightValue * 0.453592).toFixed(1);
        }
        this.elements.heightUnit.textContent = 'cm';
        this.elements.weightUnit.textContent = 'kg';
        
        // 更新輸入範圍 / Update input ranges
        heightInput.min = this.limits.metric.height.min;
        heightInput.max = this.limits.metric.height.max;
        weightInput.min = this.limits.metric.weight.min;
        weightInput.max = this.limits.metric.weight.max;
    }
    
    // 更新當前單位 / Update current unit
    this.currentUnit = unit;
    
    // 更新按鈕狀態 / Update button states
    if (unit === 'metric') {
        this.elements.metricBtn.classList.add('active');
        this.elements.imperialBtn.classList.remove('active');
    } else {
        this.elements.imperialBtn.classList.add('active');
        this.elements.metricBtn.classList.remove('active');
    }
    
    // 重新驗證輸入 / Revalidate inputs
    this.validateHeight();
    this.validateWeight();
    this.checkCalculateButton();
    
    // 如果有結果,重新計算 / Recalculate if results exist
    if (this.bmiValue && this.heightValid && this.weightValid) {
        this.handleCalculate();
    }
}

/**
 * 添加到歷史記錄(可選功能)
 * Add to history (optional feature)
 */
addToHistory(record) {
    // 可以保存到 localStorage 或發送到服務器
    // Can save to localStorage or send to server
    const history = this.getHistory();
    history.push(record);
    
    // 只保留最近 10 條記錄 / Keep only last 10 records
    if (history.length > 10) {
        history.shift();
    }
    
    // 保存到本地存儲 / Save to local storage
    try {
        localStorage.setItem('bmi_history', JSON.stringify(history));
    } catch (e) {
        console.warn('Failed to save BMI history:', e);
    }
}

/**
 * 獲取歷史記錄
 * Get history records
 */
getHistory() {
    try {
        const history = localStorage.getItem('bmi_history');
        return history ? JSON.parse(history) : [];
    } catch (e) {
        console.warn('Failed to load BMI history:', e);
        return [];
    }
}

🎨 視覺化圖表與動畫效果

功能:BMI 視覺化圖表繪製、動態指示器定位、結果動畫效果

/**
 * 顯示計算結果
 * Display calculation results
 */
displayResult() {
    const lang = this.translations[this.currentLanguage];
    
    // 顯示結果區域 / Show result area
    this.elements.result.style.display = 'block';
    
    // 顯示 BMI 值 / Display BMI value
    this.elements.bmiValue.textContent = this.bmiValue;
    
    // 顯示 BMI 分類 / Display BMI category
    const category = lang.categories[this.bmiCategory];
    this.elements.bmiCategory.textContent = category;
    
    // 設置分類顏色 / Set category color
    const categoryColor = this.bmiCategories[this.bmiCategory].color;
    this.elements.bmiCategory.style.color = categoryColor;
    this.elements.bmiValue.style.color = categoryColor;
    
    // 顯示健康建議 / Display health advice
    const advice = lang.advice[this.bmiCategory];
    this.elements.bmiAdvice.textContent = advice;
    
    // 顯示理想體重範圍 / Display ideal weight range
    const idealWeightText = `${lang.ideal_weight}: ${this.idealWeightRange.min}-${this.idealWeightRange.max} ${this.idealWeightRange.unit}`;
    this.elements.idealWeight.textContent = idealWeightText;
    
    // 更新視覺化圖表 / Update visual chart
    this.updateBMIChart();
    
    // 滾動到結果區域 / Scroll to result area
    this.elements.result.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}

/**
 * 更新 BMI 視覺化圖表
 * Update BMI visual chart
 */
updateBMIChart() {
    // 計算指示器位置 / Calculate indicator position
    const position = this.calculateIndicatorPosition(this.bmiValue);
    
    // 設置指示器位置(帶動畫)/ Set indicator position (with animation)
    this.elements.bmiIndicator.style.left = `${position}%`;
    
    // 繪製彩色條帶 / Draw colored bars
    this.drawBMIBar();
    
    // 添加刻度標記 / Add scale markers
    this.addScaleMarkers();
}

/**
 * 計算指示器在圖表上的位置
 * Calculate indicator position on chart
 * 
 * 將 BMI 值映射到 0-100% 的位置
 * Map BMI value to 0-100% position
 */
calculateIndicatorPosition(bmi) {
    // BMI 範圍通常是 15-40,映射到百分比
    // BMI range is typically 15-40, map to percentage
    const minBMI = 15;
    const maxBMI = 40;
    const range = maxBMI - minBMI;
    
    // 限制在範圍內 / Limit within range
    const clampedBMI = Math.max(minBMI, Math.min(maxBMI, bmi));
    
    // 計算百分比位置 / Calculate percentage position
    const position = ((clampedBMI - minBMI) / range) * 100;
    
    return position;
}

/**
 * 繪製 BMI 彩色條帶
 * Draw BMI colored bars
 */
drawBMIBar() {
    // 清空現有內容 / Clear existing content
    this.elements.bmiBar.innerHTML = '';
    
    // BMI 範圍和對應顏色 / BMI ranges and corresponding colors
    const ranges = [
        { start: 0, end: 18.5, color: '#3b82f6', category: 'underweight' },
        { start: 18.5, end: 25, color: '#10b981', category: 'normal' },
        { start: 25, end: 30, color: '#f59e0b', category: 'overweight' },
        { start: 30, end: 35, color: '#ef4444', category: 'obese1' },
        { start: 35, end: 40, color: '#dc2626', category: 'obese2' },
        { start: 40, end: 50, color: '#991b1b', category: 'obese3' }
    ];
    
    // 創建每個顏色段 / Create each color segment
    ranges.forEach(range => {
        const segment = document.createElement('div');
        segment.className = 'bmi-segment';
        segment.style.backgroundColor = range.color;
        
        // 計算寬度百分比 / Calculate width percentage
        const width = this.calculateSegmentWidth(range.start, range.end);
        segment.style.width = `${width}%`;
        
        // 添加到容器 / Add to container
        this.elements.bmiBar.appendChild(segment);
    });
}

/**
 * 計算顏色段的寬度
 * Calculate segment width
 */
calculateSegmentWidth(start, end) {
    const minBMI = 15;
    const maxBMI = 40;
    const totalRange = maxBMI - minBMI;
    
    // 調整範圍到顯示區域 / Adjust range to display area
    const adjustedStart = Math.max(minBMI, start);
    const adjustedEnd = Math.min(maxBMI, end);
    
    // 計算寬度百分比 / Calculate width percentage
    const width = ((adjustedEnd - adjustedStart) / totalRange) * 100;
    
    return Math.max(0, width);
}

/**
 * 添加刻度標記
 * Add scale markers
 */
addScaleMarkers() {
    // 創建刻度容器 / Create scale container
    const scaleContainer = document.createElement('div');
    scaleContainer.className = 'bmi-scale';
    
    // 主要刻度點 / Main scale points
    const scalePoints = [15, 18.5, 25, 30, 35, 40];
    
    scalePoints.forEach(point => {
        const marker = document.createElement('div');
        marker.className = 'scale-marker';
        
        const position = this.calculateIndicatorPosition(point);
        marker.style.left = `${position}%`;
        
        const label = document.createElement('span');
        label.className = 'scale-label';
        label.textContent = point;
        marker.appendChild(label);
        
        scaleContainer.appendChild(marker);
    });
    
    // 添加到圖表下方 / Add below chart
    const existingScale = this.elements.bmiBar.parentElement.querySelector('.bmi-scale');
    if (existingScale) {
        existingScale.remove();
    }
    this.elements.bmiBar.parentElement.appendChild(scaleContainer);
}

/**
 * 添加結果顯示動畫
 * Add result display animation
 */
animateResult() {
    // 結果區域淡入 / Fade in result area
    this.elements.result.style.opacity = '0';
    this.elements.result.style.transform = 'translateY(20px)';
    
    setTimeout(() => {
        this.elements.result.style.transition = 'all 0.5s ease';
        this.elements.result.style.opacity = '1';
        this.elements.result.style.transform = 'translateY(0)';
    }, 10);
    
    // BMI 值數字動畫 / BMI value number animation
    this.animateNumber(this.elements.bmiValue, 0, this.bmiValue, 1000);
    
    // 指示器滑動動畫 / Indicator sliding animation
    this.elements.bmiIndicator.style.transition = 'left 1s cubic-bezier(0.4, 0, 0.2, 1)';
    
    // 添加脈動效果到分類標籤 / Add pulse effect to category label
    this.elements.bmiCategory.classList.add('pulse');
    setTimeout(() => {
        this.elements.bmiCategory.classList.remove('pulse');
    }, 2000);
}

/**
 * 數字動畫效果
 * Number animation effect
 */
animateNumber(element, start, end, duration) {
    const startTime = performance.now();
    const range = end - start;
    
    const animate = (currentTime) => {
        const elapsed = currentTime - startTime;
        const progress = Math.min(elapsed / duration, 1);
        
        // 使用緩動函數 / Use easing function
        const easeProgress = this.easeOutQuart(progress);
        const current = start + (range * easeProgress);
        
        element.textContent = current.toFixed(1);
        
        if (progress < 1) {
            requestAnimationFrame(animate);
        }
    };
    
    requestAnimationFrame(animate);
}

/**
 * 緩動函數 - easeOutQuart
 * Easing function - easeOutQuart
 */
easeOutQuart(t) {
    return 1 - Math.pow(1 - t, 4);
}

/**
 * 清空所有輸入和結果
 * Clear all inputs and results
 */
clearAll() {
    // 清空輸入 / Clear inputs
    this.elements.heightInput.value = '';
    this.elements.weightInput.value = '';
    
    // 隱藏結果 / Hide results
    this.elements.result.style.display = 'none';
    
    // 重置狀態 / Reset states
    this.bmiValue = null;
    this.bmiCategory = null;
    this.idealWeightRange = null;
    this.heightValid = false;
    this.weightValid = false;
    
    // 禁用計算按鈕 / Disable calculate button
    this.elements.calculateBtn.disabled = true;
    
    // 清除錯誤提示 / Clear error messages
    this.clearErrors();
}

🛠️ 實用方法與驗證系統

功能:輸入驗證、格式化、錯誤處理等實用方法集合

/**
 * 驗證身高輸入
 * Validate height input
 */
validateHeight() {
    const value = parseFloat(this.elements.heightInput.value);
    const lang = this.translations[this.currentLanguage];
    const limits = this.limits[this.currentUnit].height;
    
    // 清除之前的錯誤 / Clear previous errors
    this.clearError('height');
    
    // 檢查是否為空 / Check if empty
    if (!this.elements.heightInput.value) {
        this.heightValid = false;
        return false;
    }
    
    // 檢查是否為有效數字 / Check if valid number
    if (isNaN(value)) {
        this.showError('height', lang.errors.height_required);
        this.heightValid = false;
        return false;
    }
    
    // 檢查範圍 / Check range
    if (value < limits.min || value > limits.max) {
        const unit = this.currentUnit === 'metric' ? 'cm' : 'in';
        const errorMsg = `${lang.errors.height_range} (${limits.min}-${limits.max} ${unit})`;
        this.showError('height', errorMsg);
        this.heightValid = false;
        return false;
    }
    
    this.heightValid = true;
    return true;
}

/**
 * 驗證體重輸入
 * Validate weight input
 */
validateWeight() {
    const value = parseFloat(this.elements.weightInput.value);
    const lang = this.translations[this.currentLanguage];
    const limits = this.limits[this.currentUnit].weight;
    
    // 清除之前的錯誤 / Clear previous errors
    this.clearError('weight');
    
    // 檢查是否為空 / Check if empty
    if (!this.elements.weightInput.value) {
        this.weightValid = false;
        return false;
    }
    
    // 檢查是否為有效數字 / Check if valid number
    if (isNaN(value)) {
        this.showError('weight', lang.errors.weight_required);
        this.weightValid = false;
        return false;
    }
    
    // 檢查範圍 / Check range
    if (value < limits.min || value > limits.max) {
        const unit = this.currentUnit === 'metric' ? 'kg' : 'lb';
        const errorMsg = `${lang.errors.weight_range} (${limits.min}-${limits.max} ${unit})`;
        this.showError('weight', errorMsg);
        this.weightValid = false;
        return false;
    }
    
    this.weightValid = true;
    return true;
}

/**
 * 驗證所有輸入
 * Validate all inputs
 */
validateInputs(height, weight) {
    const lang = this.translations[this.currentLanguage];
    
    // 檢查 NaN / Check for NaN
    if (isNaN(height) || isNaN(weight)) {
        this.showError('general', lang.errors.calculation_failed);
        return false;
    }
    
    // 驗證身高和體重 / Validate height and weight
    const heightValid = this.validateHeight();
    const weightValid = this.validateWeight();
    
    return heightValid && weightValid;
}

/**
 * 格式化輸入值
 * Format input value
 */
formatInput(type) {
    const input = type === 'height' ? this.elements.heightInput : this.elements.weightInput;
    const value = parseFloat(input.value);
    
    if (!isNaN(value)) {
        // 格式化為一位小數 / Format to one decimal place
        input.value = value.toFixed(1);
    }
}

/**
 * 顯示錯誤信息
 * Show error message
 */
showError(field, message) {
    if (field === 'height' && this.elements.heightError) {
        this.elements.heightError.textContent = message;
        this.elements.heightError.style.display = 'block';
        this.elements.heightInput.classList.add('error');
    } else if (field === 'weight' && this.elements.weightError) {
        this.elements.weightError.textContent = message;
        this.elements.weightError.style.display = 'block';
        this.elements.weightInput.classList.add('error');
    } else if (field === 'general') {
        // 顯示通用錯誤提示 / Show general error message
        this.showToast(message, 'error');
    }
}

/**
 * 清除錯誤信息
 * Clear error message
 */
clearError(field) {
    if (field === 'height' && this.elements.heightError) {
        this.elements.heightError.style.display = 'none';
        this.elements.heightInput.classList.remove('error');
    } else if (field === 'weight' && this.elements.weightError) {
        this.elements.weightError.style.display = 'none';
        this.elements.weightInput.classList.remove('error');
    }
}

/**
 * 清除所有錯誤
 * Clear all errors
 */
clearErrors() {
    this.clearError('height');
    this.clearError('weight');
}

/**
 * 顯示提示信息(Toast)
 * Show toast message
 */
showToast(message, type = 'info') {
    // 創建 toast 元素 / Create toast element
    const toast = document.createElement('div');
    toast.className = `toast toast-${type}`;
    toast.textContent = message;
    
    // 添加到頁面 / Add to page
    document.body.appendChild(toast);
    
    // 顯示動畫 / Show animation
    setTimeout(() => {
        toast.classList.add('show');
    }, 10);
    
    // 3秒後自動消失 / Auto hide after 3 seconds
    setTimeout(() => {
        toast.classList.remove('show');
        setTimeout(() => {
            toast.remove();
        }, 300);
    }, 3000);
}

/**
 * 顯示初始化錯誤
 * Show initialization error
 */
showInitializationError() {
    const lang = this.translations[this.currentLanguage];
    const errorContainer = document.createElement('div');
    errorContainer.className = 'initialization-error';
    errorContainer.innerHTML = `
        

${lang.errors.calculation_failed}

`; // 找到主容器並添加錯誤信息 / Find main container and add error message const mainContainer = document.querySelector('.bmi-calculator-standalone'); if (mainContainer) { mainContainer.appendChild(errorContainer); } } /** * 防抖函數 * Debounce function */ debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } /** * 節流函數 * Throttle function */ throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } /** * 複製結果到剪貼板 * Copy result to clipboard */ copyResult() { const lang = this.translations[this.currentLanguage]; const bmiText = `BMI: ${this.bmiValue}`; const categoryText = `${lang.categories[this.bmiCategory]}`; const idealWeightText = `${lang.ideal_weight}: ${this.idealWeightRange.min}-${this.idealWeightRange.max} ${this.idealWeightRange.unit}`; const fullText = `${bmiText}\n${categoryText}\n${idealWeightText}`; // 複製到剪貼板 / Copy to clipboard navigator.clipboard.writeText(fullText).then(() => { this.showToast(lang.copied || 'Copied!', 'success'); }).catch(err => { console.error('Failed to copy:', err); this.showToast(lang.errors.network_error, 'error'); }); } /** * 導出結果為圖片(可選功能) * Export result as image (optional feature) */ exportAsImage() { // 使用 html2canvas 或類似庫生成圖片 // Use html2canvas or similar library to generate image // 這裡只是示例框架 // This is just an example framework console.log('Export as image feature not implemented'); }

🚀 工具初始化與啟動

功能:工具的完整初始化流程,包括 DOM 載入檢測、實例創建和錯誤處理

/**
 * BMI 計算器初始化啟動代碼
 * BMI Calculator initialization startup code
 */

// 等待 DOM 完全載入 / Wait for DOM to be fully loaded
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializeBMICalculator);
} else {
    // DOM 已載入,直接初始化 / DOM already loaded, initialize directly
    initializeBMICalculator();
}

/**
 * 初始化 BMI 計算器
 * Initialize BMI Calculator
 */
function initializeBMICalculator() {
    try {
        // 檢查是否在正確的頁面 / Check if on correct page
        const bmiContainer = document.querySelector('.bmi-calculator-standalone');
        if (!bmiContainer) {
            console.log('BMI Calculator container not found, skipping initialization');
            return;
        }
        
        // 創建 BMI 計算器實例 / Create BMI Calculator instance
        window.bmiCalculator = new BmiCalculator();
        
        // 添加全局訪問方法(可選)/ Add global access methods (optional)
        window.BMI = {
            calculate: (height, weight, unit = 'metric') => {
                window.bmiCalculator.currentUnit = unit;
                window.bmiCalculator.calculateBMI(height, weight);
                return window.bmiCalculator.bmiValue;
            },
            
            getCategory: (bmi) => {
                return window.bmiCalculator.getBMICategory(bmi);
            },
            
            clear: () => {
                window.bmiCalculator.clearAll();
            },
            
            switchLanguage: (lang) => {
                window.bmiCalculator.currentLanguage = lang;
                window.bmiCalculator.updateLanguage();
            },
            
            getHistory: () => {
                return window.bmiCalculator.getHistory();
            }
        };
        
        console.log('BMI Calculator initialized successfully');
        
        // 發送初始化完成事件 / Send initialization complete event
        const event = new CustomEvent('bmiCalculatorReady', {
            detail: { calculator: window.bmiCalculator }
        });
        window.dispatchEvent(event);
        
    } catch (error) {
        console.error('Failed to initialize BMI Calculator:', error);
        
        // 顯示用戶友好的錯誤信息 / Show user-friendly error message
        showInitializationError();
    }
}

/**
 * 顯示初始化錯誤(備用方法)
 * Show initialization error (fallback method)
 */
function showInitializationError() {
    const container = document.querySelector('.bmi-calculator-standalone');
    if (container) {
        container.innerHTML = `
            

⚠️ 載入錯誤 / Loading Error

BMI 計算器載入失敗,請重新整理頁面。

BMI Calculator failed to load, please refresh the page.

`; } } /** * 工具卸載清理(可選) * Tool cleanup on unload (optional) */ window.addEventListener('beforeunload', () => { // 保存當前狀態到 localStorage(如果需要) // Save current state to localStorage (if needed) if (window.bmiCalculator && window.bmiCalculator.bmiValue) { try { const lastCalculation = { bmi: window.bmiCalculator.bmiValue, category: window.bmiCalculator.bmiCategory, unit: window.bmiCalculator.currentUnit, timestamp: new Date().toISOString() }; localStorage.setItem('bmi_last_calculation', JSON.stringify(lastCalculation)); } catch (e) { // 忽略存儲錯誤 / Ignore storage errors } } }); /** * 響應式設計支援 * Responsive design support */ window.addEventListener('resize', debounce(() => { if (window.bmiCalculator && window.bmiCalculator.elements.result.style.display !== 'none') { // 重新調整圖表大小 / Readjust chart size window.bmiCalculator.updateBMIChart(); } }, 250)); /** * 性能優化:延遲載入非關鍵功能 * Performance optimization: Lazy load non-critical features */ setTimeout(() => { // 載入歷史記錄功能 / Load history feature if (window.bmiCalculator) { const history = window.bmiCalculator.getHistory(); if (history.length > 0) { console.log(`Found ${history.length} previous calculations`); } } // 預載入可能需要的資源 / Preload potentially needed resources const link = document.createElement('link'); link.rel = 'prefetch'; link.href = '/assets/bmi-chart-bg.svg'; document.head.appendChild(link); }, 2000); /** * 開發模式調試助手 * Development mode debug helper */ if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { window.BMI_DEBUG = { getState: () => { if (!window.bmiCalculator) return null; return { language: window.bmiCalculator.currentLanguage, unit: window.bmiCalculator.currentUnit, gender: window.bmiCalculator.currentGender, bmi: window.bmiCalculator.bmiValue, category: window.bmiCalculator.bmiCategory, heightValid: window.bmiCalculator.heightValid, weightValid: window.bmiCalculator.weightValid }; }, testCalculation: () => { window.bmiCalculator.elements.heightInput.value = '170'; window.bmiCalculator.elements.weightInput.value = '70'; window.bmiCalculator.validateHeight(); window.bmiCalculator.validateWeight(); window.bmiCalculator.checkCalculateButton(); window.bmiCalculator.handleCalculate(); }, testUnitSwitch: () => { const currentUnit = window.bmiCalculator.currentUnit; const newUnit = currentUnit === 'metric' ? 'imperial' : 'metric'; window.bmiCalculator.switchUnit(newUnit); console.log(`Switched from ${currentUnit} to ${newUnit}`); } }; console.log('BMI Calculator Debug Mode Enabled. Use window.BMI_DEBUG for testing.'); } // 簡單的防抖實現(如果尚未定義)/ Simple debounce implementation (if not already defined) function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }

效能優化策略

  • 即時計算:採用輕量級計算,無需服務器請求,毫秒級響應
  • 防抖處理:輸入驗證使用防抖技術,避免頻繁計算
  • DOM 緩存:所有 DOM 元素引用在初始化時緩存,避免重複查詢
  • 動畫優化:使用 CSS 過渡和 requestAnimationFrame 確保流暢動畫
  • 延遲載入:非關鍵功能延遲 2 秒載入,加快初始渲染

安全性考量

  • 純本地處理:所有計算在瀏覽器端完成,無數據外洩風險
  • 輸入驗證:嚴格的輸入範圍檢查,防止異常數據
  • XSS 防護:所有用戶輸入使用 textContent 而非 innerHTML
  • 本地存儲安全:歷史記錄僅存儲必要數據,定期清理

瀏覽器兼容性

瀏覽器 最低版本 支援程度
Chrome 60+ ✅ 完全支援
Firefox 55+ ✅ 完全支援
Safari 12+ ✅ 完全支援
Edge 79+ ✅ 完全支援
Mobile Browsers iOS 12+ / Android 5+ ✅ 完全支援

未來改進方向

  • 體脂率計算:加入更精確的體脂率估算功能
  • 歷史趨勢圖表:視覺化展示 BMI 變化趨勢
  • 個性化建議:基於年齡、性別的更精準健康建議
  • 數據導出:支援 PDF 報告生成和數據導出
  • 多語言擴展:支援更多語言版本

總結

BMI 計算器展示了如何使用純前端技術構建一個功能完整、用戶友好的健康評估工具。 通過模組化設計、完善的錯誤處理、優雅的動畫效果,為用戶提供了專業級的使用體驗。 工具的雙語支援和響應式設計確保了廣泛的可訪問性,而純本地處理則最大程度保護了用戶隱私。

🎯 技術亮點總結

  • 零依賴純 JavaScript 實現
  • 完整的雙單位系統支援
  • 專業的視覺化圖表展示
  • 全面的輸入驗證和錯誤處理
  • 流暢的動畫和用戶體驗