pin_drop当前位置:知识文库 ❯ 图文
HTML5 API:Battery API - 完整教程与代码示例
一、教程简介
Battery Status API(又称 Battery API)允许 Web 应用获取设备的电池状态信息,包括电池电量、充电状态、剩余充电/放电时间等。通过这些信息,开发者可以根据电池状态动态调整应用行为,例如在低电量时减少动画效果、降低请求频率、切换到省电模式等,从而优化电池使用效率并提升用户体验。
提示:由于隐私考虑,部分现代浏览器已经限制或移除了 Battery API 的支持。Firefox 在版本 52 后移除了该 API,Chrome 仍然支持但可能需要用户授权。
二、核心概念
1. BatteryManager 对象
BatteryManager 是 Battery API 的核心接口,通过 navigator.getBattery() 方法获取。
代码示例
// 获取 BatteryManager 对象
const battery = await navigator.getBattery();
// BatteryManager 的属性
console.log(battery.charging); // 是否正在充电(boolean)
console.log(battery.chargingTime); // 充满电所需秒数(Infinity 表示未充电或无法确定)
console.log(battery.dischargingTime); // 放电完毕所需秒数(Infinity 表示正在充电或无法确定)
console.log(battery.level); // 当前电量(0.0 - 1.0)2. 电池事件
BatteryManager 提供了四个事件用于监听电池状态变化:
代码示例
// 充电状态变化
battery.addEventListener('chargingchange', () => {
console.log('充电状态:', battery.charging);
});
// 充电时间变化
battery.addEventListener('chargingtimechange', () => {
console.log('充满所需时间:', battery.chargingTime);
});
// 放电时间变化
battery.addEventListener('dischargingtimechange', () => {
console.log('剩余使用时间:', battery.dischargingTime);
});
// 电量变化
battery.addEventListener('levelchange', () => {
console.log('当前电量:', (battery.level * 100).toFixed(0) + '%');
});3. 电量级别
代码示例
// battery.level 返回 0.0 到 1.0 之间的值
// 常见的电量级别划分:
// - 1.0: 满电
// - 0.5: 半电
// - 0.2: 低电量
// - 0.0: 电量耗尽
function getBatteryStatus(level) {
if (level > 0.8) return '电量充足';
if (level > 0.5) return '电量正常';
if (level > 0.2) return '电量偏低';
return '电量不足';
}三、语法与用法
获取电池信息
代码示例
// 异步获取 BatteryManager
navigator.getBattery()
.then(battery => {
console.log('电量:', battery.level);
console.log('充电状态:', battery.charging);
})
.catch(error => {
console.error('无法获取电池信息:', error);
});
// 使用 async/await
async function getBatteryInfo() {
try {
const battery = await navigator.getBattery();
return {
level: battery.level,
charging: battery.charging,
chargingTime: battery.chargingTime,
dischargingTime: battery.dischargingTime
};
} catch (error) {
console.error('Battery API 不可用:', error);
return null;
}
}监听电池事件
代码示例
async function monitorBattery() {
const battery = await navigator.getBattery();
// 充电状态变化
battery.addEventListener('chargingchange', updateUI);
// 电量变化
battery.addEventListener('levelchange', updateUI);
// 充电/放电时间变化
battery.addEventListener('chargingtimechange', updateUI);
battery.addEventListener('dischargingtimechange', updateUI);
function updateUI() {
console.log({
level: battery.level,
charging: battery.charging,
chargingTime: battery.chargingTime,
dischargingTime: battery.dischargingTime
});
}
}兼容性检测
代码示例
// 检测 Battery API 支持
if ('getBattery' in navigator) {
navigator.getBattery().then(battery => {
// Battery API 可用
});
} else {
console.log('当前浏览器不支持 Battery API');
}四、代码示例
示例1:电池状态监控面板
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Battery API - 电池状态监控面板</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #0c0c1d, #1a1a3e);
color: #e0e0e0; min-height: 100vh;
display: flex; justify-content: center;
align-items: center; padding: 20px;
}
.panel {
background: rgba(255,255,255,0.05);
border-radius: 25px; padding: 40px;
width: 420px; max-width: 100%;
backdrop-filter: blur(10px);
}
h1 { text-align: center; color: #2ecc71; margin-bottom: 30px; }
.battery-visual { display: flex; justify-content: center; margin-bottom: 30px; }
.battery-icon {
width: 160px; height: 80px;
border: 4px solid #e0e0e0;
border-radius: 10px; position: relative; overflow: hidden;
}
.battery-icon::after {
content: ''; position: absolute; right: -12px;
top: 50%; transform: translateY(-50%);
width: 8px; height: 30px;
background: #e0e0e0; border-radius: 0 4px 4px 0;
}
.battery-fill {
height: 100%; transition: width 0.5s, background-color 0.5s;
position: relative;
}
.battery-fill.charging { animation: chargingPulse 2s infinite; }
@keyframes chargingPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
.battery-percent {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
font-size: 24px; font-weight: bold;
color: white; text-shadow: 0 1px 3px rgba(0,0,0,0.5);
}
.status-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 25px; }
.status-card { background: rgba(255,255,255,0.05); border-radius: 12px; padding: 15px; text-align: center; }
.status-card .label { font-size: 12px; color: #888; margin-bottom: 5px; }
.status-card .value { font-size: 18px; font-weight: bold; }
.status-card .value.charging { color: #2ecc71; }
.status-card .value.discharging { color: #e74c3c; }
.status-card .value.time { color: #3498db; }
</style>
</head>
<body>
<div class="panel">
<h1>Battery Monitor</h1>
<div class="battery-visual">
<div class="battery-icon">
<div class="battery-fill" id="batteryFill">
<span class="battery-percent" id="batteryPercent">--%</span>
</div>
</div>
</div>
<div class="status-grid">
<div class="status-card">
<div class="label">充电状态</div>
<div class="value" id="chargingStatus">--</div>
</div>
<div class="status-card">
<div class="label">电量</div>
<div class="value" id="levelValue">--%</div>
</div>
<div class="status-card">
<div class="label">充满时间</div>
<div class="value time" id="chargingTime">--</div>
</div>
<div class="status-card">
<div class="label">剩余时间</div>
<div class="value time" id="dischargingTime">--</div>
</div>
</div>
</div>
<script>
async function initBattery() {
const battery = await navigator.getBattery();
function updateUI() {
const percent = (battery.level * 100).toFixed(0);
document.getElementById('batteryFill').style.width = percent + '%';
document.getElementById('batteryPercent').textContent = percent + '%';
document.getElementById('levelValue').textContent = percent + '%';
document.getElementById('chargingStatus').textContent = battery.charging ? '充电中' : '未充电';
}
battery.addEventListener('chargingchange', updateUI);
battery.addEventListener('levelchange', updateUI);
updateUI();
}
initBattery();
</script>
</body>
</html>示例2:省电模式管理器
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Battery API - 省电模式管理器</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #1a1a2e; color: #e0e0e0; min-height: 100vh; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; }
h1 { color: #f39c12; text-align: center; margin-bottom: 25px; }
.mode-banner {
padding: 15px 20px; border-radius: 12px;
text-align: center; margin-bottom: 25px;
font-weight: bold; transition: all 0.5s;
}
.mode-banner.normal { background: rgba(46,204,113,0.15); border: 1px solid rgba(46,204,113,0.3); color: #2ecc71; }
.mode-banner.saving { background: rgba(243,156,18,0.15); border: 1px solid rgba(243,156,18,0.3); color: #f39c12; }
.mode-banner.critical { background: rgba(231,76,60,0.15); border: 1px solid rgba(231,76,60,0.3); color: #e74c3c; }
.battery-bar { height: 30px; background: rgba(255,255,255,0.1); border-radius: 15px; overflow: hidden; margin-bottom: 20px; position: relative; }
.battery-bar-fill { height: 100%; border-radius: 15px; transition: width 0.5s; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: bold; color: white; }
</style>
</head>
<body>
<div class="container">
<h1>Power Saving Manager</h1>
<div class="mode-banner normal" id="modeBanner">正常模式</div>
<div class="battery-bar">
<div class="battery-bar-fill" id="batteryBarFill">--</div>
</div>
</div>
<script>
async function init() {
if ('getBattery' in navigator) {
const battery = await navigator.getBattery();
function updateUI() {
const level = battery.level * 100;
const barFill = document.getElementById('batteryBarFill');
barFill.style.width = level + '%';
barFill.textContent = level.toFixed(0) + '%';
const banner = document.getElementById('modeBanner');
if (battery.charging || level > 20) {
banner.className = 'mode-banner normal';
banner.textContent = '正常模式';
} else if (level > 10) {
banner.className = 'mode-banner saving';
banner.textContent = '省电模式';
} else {
banner.className = 'mode-banner critical';
banner.textContent = '紧急模式';
}
}
battery.addEventListener('levelchange', updateUI);
battery.addEventListener('chargingchange', updateUI);
updateUI();
}
}
init();
</script>
</body>
</html>示例3:电池感知的自适应应用
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Battery API - 电池感知自适应应用</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #1e272e; color: #d2dae2; min-height: 100vh; padding: 20px; }
.container { max-width: 700px; margin: 0 auto; }
h1 { color: #0fbcf9; text-align: center; margin-bottom: 20px; }
.battery-strip { display: flex; align-items: center; gap: 10px; padding: 12px 20px; background: rgba(255,255,255,0.05); border-radius: 12px; margin-bottom: 25px; }
.mode { margin-left: auto; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: bold; }
.mode.high { background: rgba(0,184,148,0.2); color: #00b894; }
.mode.medium { background: rgba(253,203,110,0.2); color: #fdcb6e; }
.mode.low { background: rgba(214,48,49,0.2); color: #d63031; }
.content-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.content-card { background: rgba(255,255,255,0.05); border-radius: 12px; padding: 20px; transition: all 0.3s; }
.content-card.disabled { opacity: 0.3; pointer-events: none; }
</style>
</head>
<body>
<div class="container">
<h1>Adaptive App</h1>
<div class="battery-strip">
<span class="level" id="batteryLevel">检测中...</span>
<span class="mode high" id="powerMode">高性能</span>
</div>
<div class="content-grid">
<div class="content-card" id="cardImages">高清图片</div>
<div class="content-card" id="cardAnimation">动画效果</div>
<div class="content-card" id="cardRefresh">实时刷新</div>
<div class="content-card" id="cardSync">后台同步</div>
</div>
</div>
<script>
async function init() {
if ('getBattery' in navigator) {
const battery = await navigator.getBattery();
function adapt() {
const level = battery.level;
const charging = battery.charging;
document.getElementById('batteryLevel').textContent = `${(level * 100).toFixed(0)}% ${charging ? '(充电中)' : ''}`;
const modeEl = document.getElementById('powerMode');
if (charging || level > 0.5) {
modeEl.textContent = '高性能'; modeEl.className = 'mode high';
document.querySelectorAll('.content-card').forEach(c => c.classList.remove('disabled'));
} else if (level > 0.2) {
modeEl.textContent = '均衡'; modeEl.className = 'mode medium';
document.getElementById('cardSync').classList.add('disabled');
} else {
modeEl.textContent = '省电'; modeEl.className = 'mode low';
document.querySelectorAll('.content-card').forEach(c => c.classList.add('disabled'));
}
}
battery.addEventListener('levelchange', adapt);
battery.addEventListener('chargingchange', adapt);
adapt();
}
}
init();
</script>
</body>
</html>示例4:电池历史记录
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Battery API - 电池历史记录</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #0a0a1a; color: #e0e0e0; min-height: 100vh; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; }
h1 { color: #6c5ce7; text-align: center; margin-bottom: 25px; }
.current-status { display: flex; align-items: center; gap: 20px; padding: 20px; background: rgba(255,255,255,0.05); border-radius: 15px; margin-bottom: 25px; }
.battery-circle { width: 100px; height: 100px; border-radius: 50%; border: 6px solid rgba(255,255,255,0.1); display: flex; align-items: center; justify-content: center; font-size: 28px; font-weight: bold; }
.history-list { background: rgba(255,255,255,0.05); border-radius: 15px; padding: 15px; max-height: 200px; overflow-y: auto; }
.history-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 13px; }
.history-item .time { color: #636e72; }
.history-item .level { font-weight: bold; }
.history-item .event { color: #6c5ce7; }
</style>
</head>
<body>
<div class="container">
<h1>Battery History</h1>
<div class="current-status">
<div class="battery-circle" id="circlePercent">--%</div>
<div class="status-info">
<h2 id="statusTitle">检测中...</h2>
<p id="statusDetail">等待电池信息</p>
</div>
</div>
<div class="history-list">
<h3>事件记录</h3>
<div id="historyItems"></div>
</div>
</div>
<script>
const history = [];
async function init() {
if ('getBattery' in navigator) {
const battery = await navigator.getBattery();
function record(event) {
history.push({ time: Date.now(), level: battery.level, event });
document.getElementById('circlePercent').textContent = (battery.level * 100).toFixed(0) + '%';
document.getElementById('statusTitle').textContent = battery.charging ? '充电中' : '使用电池';
const container = document.getElementById('historyItems');
container.innerHTML = '';
history.slice(-10).reverse().forEach(entry => {
const item = document.createElement('div');
item.className = 'history-item';
const d = new Date(entry.time);
const t = d.getHours().toString().padStart(2,'0') + ':' + d.getMinutes().toString().padStart(2,'0') + ':' + d.getSeconds().toString().padStart(2,'0');
item.innerHTML = `<span class="time">${t}</span><span class="level">${(entry.level*100).toFixed(0)}%</span><span class="event">${entry.event}</span>`;
container.appendChild(item);
});
}
record('初始');
battery.addEventListener('levelchange', () => record('电量变化'));
battery.addEventListener('chargingchange', () => record(battery.charging ? '开始充电' : '停止充电'));
setInterval(() => record('定时记录'), 60000);
}
}
init();
</script>
</body>
</html>五、浏览器兼容性
兼容性注意事项
代码示例
// 1. Firefox 已移除 Battery API
// 原因:隐私问题,电池信息可能被用于指纹追踪
// 2. Safari 从未支持 Battery API
// Apple 认为电池信息属于敏感隐私数据
// 3. Chrome 仍然支持,但可能需要安全上下文
// Battery API 需要在 HTTPS 或 localhost 下使用
// 4. 完整的兼容性检测
async function checkBatterySupport() {
if (!('getBattery' in navigator)) {
return { supported: false, reason: 'API 不存在' };
}
try {
const battery = await navigator.getBattery();
return { supported: true, battery };
} catch (error) {
return { supported: false, reason: error.message };
}
}六、注意事项与最佳实践
1. 隐私考虑
代码示例
// Battery API 存在隐私风险
// 电池信息可能被用于用户指纹追踪
// 最佳实践:
// 1. 仅在必要时请求电池信息
// 2. 不要将电池信息发送到服务器用于追踪
// 3. 提供用户选择退出的选项2. 优雅降级
代码示例
// Battery API 不可用时的降级策略
class AdaptiveApp {
constructor() {
this.batteryLevel = 1; // 默认满电
this.isCharging = true; // 默认充电中
this.batteryAvailable = false;
}
async init() {
if ('getBattery' in navigator) {
try {
const battery = await navigator.getBattery();
this.batteryLevel = battery.level;
this.isCharging = battery.charging;
this.batteryAvailable = true;
battery.addEventListener('levelchange', () => {
this.batteryLevel = battery.level;
this.adapt();
});
} catch (e) {
console.log('Battery API 不可用,使用默认设置');
}
}
this.adapt();
}
adapt() {
if (this.batteryAvailable) {
if (this.isCharging || this.batteryLevel > 0.5) {
this.setHighPerformance();
} else if (this.batteryLevel > 0.2) {
this.setBalanced();
} else {
this.setPowerSaving();
}
} else {
// 使用用户偏好或默认设置
const savedMode = localStorage.getItem('power_mode') || 'balanced';
switch (savedMode) {
case 'high': this.setHighPerformance(); break;
case 'balanced': this.setBalanced(); break;
case 'saving': this.setPowerSaving(); break;
}
}
}
setHighPerformance() { /* 高性能模式 */ }
setBalanced() { /* 均衡模式 */ }
setPowerSaving() { /* 省电模式 */ }
}3. 避免频繁轮询
代码示例
// 不好的做法:频繁轮询电池状态
// setInterval(() => {
// navigator.getBattery().then(b => console.log(b.level));
// }, 1000); // 不需要,使用事件监听即可
// 好的做法:使用事件监听
async function monitorBattery() {
const battery = await navigator.getBattery();
battery.addEventListener('levelchange', () => {
console.log('电量变化:', battery.level);
});
battery.addEventListener('chargingchange', () => {
console.log('充电状态变化:', battery.charging);
});
}4. 安全上下文要求
代码示例
// Battery API 需要在安全上下文中使用
// 即 HTTPS 或 localhost
if (window.isSecureContext) {
// 可以使用 Battery API
navigator.getBattery().then(battery => {
// ...
});
} else {
console.warn('Battery API 需要安全上下文 (HTTPS)');
}七、代码规范示例
完整的电池管理封装
代码示例
/**
* BatteryMonitor - 电池状态管理器
* 封装 Battery API,提供省电模式、事件监听、优雅降级等功能
*/
class BatteryMonitor {
static MODE = { HIGH: 'high', BALANCED: 'balanced', SAVING: 'saving', CRITICAL: 'critical' };
static DEFAULTS = { savingThreshold: 0.2, criticalThreshold: 0.1, autoAdapt: true, respectCharging: true };
constructor(options = {}) {
this.config = { ...BatteryMonitor.DEFAULTS, ...options };
this.battery = null;
this.currentMode = BatteryMonitor.MODE.HIGH;
this.listeners = new Map();
this.available = false;
}
async init() {
if (!('getBattery' in navigator)) {
this.available = false;
this._emit('unavailable');
return this;
}
try {
this.battery = await navigator.getBattery();
this.available = true;
this.battery.addEventListener('chargingchange', () => { this._updateMode(); this._emit('chargingchange', this.getStatus()); });
this.battery.addEventListener('levelchange', () => { this._updateMode(); this._emit('levelchange', this.getStatus()); });
this._updateMode();
this._emit('ready', this.getStatus());
} catch (error) {
this.available = false;
this._emit('error', error);
}
return this;
}
getStatus() {
if (!this.available) return { available: false, level: null, charging: null, mode: this.currentMode };
return { available: true, level: this.battery.level, charging: this.battery.charging, chargingTime: this.battery.chargingTime, dischargingTime: this.battery.dischargingTime, mode: this.currentMode };
}
_updateMode() {
if (!this.available || !this.config.autoAdapt) return;
const { level, charging } = this.battery;
let newMode;
if (this.config.respectCharging && charging) newMode = BatteryMonitor.MODE.HIGH;
else if (level > this.config.savingThreshold) newMode = BatteryMonitor.MODE.HIGH;
else if (level > this.config.criticalThreshold) newMode = BatteryMonitor.MODE.SAVING;
else newMode = BatteryMonitor.MODE.CRITICAL;
if (newMode !== this.currentMode) {
const oldMode = this.currentMode;
this.currentMode = newMode;
this._emit('modechange', { oldMode, newMode, level, charging });
}
}
on(event, callback) {
if (!this.listeners.has(event)) this.listeners.set(event, []);
this.listeners.get(event).push(callback);
return this;
}
_emit(event, data) {
if (this.listeners.has(event)) this.listeners.get(event).forEach(cb => cb(data));
}
}
// 使用示例
const batteryMonitor = new BatteryMonitor({ savingThreshold: 0.2, criticalThreshold: 0.1 });
batteryMonitor.on('ready', (status) => console.log('电池监控已启动:', status))
.on('modechange', ({ oldMode, newMode }) => console.log(`模式切换: ${oldMode} -> ${newMode}`));
batteryMonitor.init();八、常见问题与解决方案
常见问题
Firefox 和 Safari 不支持 Battery API 怎么办?
Firefox 52+ 和 Safari 不支持 Battery API。解决方案是提供降级方案,使用用户偏好设置或默认设置来替代。当 Battery API 不可用时,可以读取 localStorage 中保存的用户电源模式偏好,或使用默认的均衡模式。
chargingTime 和 dischargingTime 返回 Infinity 是什么原因?
chargingTime 为 Infinity 表示未在充电或无法确定充满时间;dischargingTime 为 Infinity 表示正在充电或无法确定放电时间。这是正常行为,需要使用 isFinite() 进行判断后再显示。
Battery API 存在隐私问题吗?
是的,电池状态(电量+充电状态+时间)的组合可能被用于用户指纹追踪。建议降低精度(将电量分为4个级别而非使用精确值),仅在用户授权后使用,不将电池数据发送到第三方服务器,并提供用户选择退出的机制。
Battery API 需要在 HTTPS 环境下使用吗?
是的,Battery API 需要在安全上下文中使用,即 HTTPS 或 localhost 环境。在 HTTP 环境下调用 navigator.getBattery() 会失败。可以通过 window.isSecureContext 检测当前是否为安全上下文。
如何正确监听电池状态变化?
应使用事件监听而非轮询。BatteryManager 提供了 chargingchange、levelchange、chargingtimechange、dischargingtimechange 四个事件,通过 addEventListener 监听即可实时获取状态变化,无需使用 setInterval 轮询。
九、总结
Battery API 为 Web 应用提供了获取设备电池状态的能力,主要特点包括:
-
实时电池信息:可以获取电量、充电状态、充电/放电时间等信息。
-
事件驱动:通过事件监听可以实时响应电池状态变化,无需轮询。
-
省电优化:根据电池状态动态调整应用行为,优化电池使用效率。
-
隐私挑战:由于隐私考虑,Firefox 和 Safari 已不支持此 API,Chrome 仍然支持。
在使用 Battery API 时,需要注意:
-
兼容性有限:仅 Chrome 和 Edge 支持,需要提供降级方案
-
隐私风险:电池信息可能被用于指纹追踪,应谨慎处理
-
安全上下文:需要在 HTTPS 环境下使用
-
优雅降级:Battery API 不可用时,应使用用户偏好或默认设置
-
事件优先:使用事件监听而非轮询来获取状态变化
通过合理的设计和封装,Battery API 可以帮助 Web 应用在移动设备上提供更好的电池使用体验,同时兼顾用户隐私。
本文涉及AI创作
内容由AI创作,请仔细甄别