概述
BMI 計算器是一個功能強大的線上健康評估工具,專為關注健康的用戶設計,能夠計算身體質量指數(BMI)、提供健康評估、顯示理想體重範圍。 本工具採用純前端架構,完全在瀏覽器端運行,零網路傳輸,確保數據的絕對隱私和安全性。 提供公制/英制雙系統切換、即時計算、視覺化圖表、個性化健康建議等專業功能。
🔒 隱私保護承諾
🔒 Privacy Protection Promise
所有 BMI 計算和健康數據處理都在您的瀏覽器本地進行,數據不會上傳到任何伺服器,確保您的健康資料完全安全。
技術架構與核心設計
整體架構設計
Overall Architecture Design
技術項目 | 實作方式 | 設計考量 |
---|---|---|
前端框架 | 純 JavaScript (ES6+) | 零依賴,最小化載入時間 |
計算引擎 | 即時計算 + 雙單位系統 | 精確度與靈活性並重 |
數據視覺化 | SVG 動態圖表 + CSS 動畫 | 輕量級且高效能 |
健康評估系統 | WHO 標準 + 多級別分類 | 國際標準與準確性 |
用戶介面 | 響應式設計 + 雙語系統 | 跨設備兼容性與國際化 |
核心類別架構
Core Class Architecture
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 結果提供針對性的健康建議
- ⚖️ 理想體重範圍:自動計算並顯示個人理想體重範圍
- 🌐 完整雙語支援:中英文介面無縫切換,包含所有提示和建議
- 📱 響應式設計:完美適配手機、平板、桌面各種設備
⚠️ 醫療聲明
⚠️ Medical Disclaimer
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 計算器展示了如何使用純前端技術構建一個功能完整、用戶友好的健康評估工具。 通過模組化設計、完善的錯誤處理、優雅的動畫效果,為用戶提供了專業級的使用體驗。 工具的雙語支援和響應式設計確保了廣泛的可訪問性,而純本地處理則最大程度保護了用戶隱私。
🎯 技術亮點總結
🎯 Technical Highlights Summary
- 零依賴純 JavaScript 實現
- 完整的雙單位系統支援
- 專業的視覺化圖表展示
- 全面的輸入驗證和錯誤處理
- 流暢的動畫和用戶體驗