页面可见性与设备状态 API(Page Visibility and Device State APIs)
一、Page Visibility API
1.1 为什么需要检测页面可见性
页面可见性 API 允许检测页面是否对用户可见。这对于优化性能和资源使用非常重要:
- 暂停视频/音频:用户切换标签页时暂停播放
- 暂停轮询:页面不可见时停止数据轮询
- 暂停动画:节省 CPU 和电池
- 记录分析:准确计算用户真实浏览时间
// 检测页面是否可见
console.log('Visibility state:', document.visibilityState);
// 'visible' - 页面完全可见
// 'hidden' - 页面完全不可见
// 'prerender' - 页面正在预渲染(很少使用)
console.log('Hidden:', document.hidden);
// true 表示页面不可见
1.2 visibilitychange 事件
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
console.log('Page is now visible');
// 恢复视频播放
// 重新开始数据轮询
// 恢复动画
} else {
console.log('Page is now hidden');
// 暂停视频播放
// 停止数据轮询
// 暂停动画
}
});
1.3 实际应用场景
class VisibilityAwarePlayer {
constructor(video) {
this.video = video;
this.wasPlaying = false;
document.addEventListener('visibilitychange', () => {
this.handleVisibilityChange();
});
}
handleVisibilityChange() {
if (document.visibilityState === 'visible') {
// 页面可见时恢复播放
if (this.wasPlaying) {
this.video.play();
}
} else {
// 页面不可见时暂停
this.wasPlaying = !this.video.paused;
this.video.pause();
}
}
}
// 使用
const player = new VisibilityAwarePlayer(document.getElementById('video'));
二、DeviceOrientationEvent
2.1 设备方向检测
// 检测支持
if ('DeviceOrientationEvent' in window) {
console.log('Device orientation supported');
}
// 添加监听器
window.addEventListener('deviceorientation', (event) => {
console.log('Alpha (Z轴):', event.alpha); // 绕 Z 轴旋转 0-360
console.log('Beta (X轴):', event.beta); // 绕 X 轴旋转 -180 到 180
console.log('Gamma (Y轴):', event.gamma); // 绕 Y 轴旋转 -90 到 90
});
2.2 设备方向坐标系
设备坐标系:
┌───┐
│ ▲ │ 前面
└───┘
│ Z
│
│
────┼──── X(向右)
│
│
↓ Y(向下)
Beta = 前后倾斜(绕 X 轴)
Gamma = 左右倾斜(绕 Y 轴)
Alpha = 指南针方向(绕 Z 轴,相对于磁北)
2.3 实际应用:虚拟现实视角
class OrientationController {
constructor(element) {
this.element = element;
this.rotation = { x: 0, y: 0, z: 0 };
this.init();
}
init() {
window.addEventListener('deviceorientation', (event) => {
this.handleOrientation(event);
});
// iOS 13+ 需要请求权限
if (typeof DeviceOrientationEvent.requestPermission === 'function') {
this.requestPermission();
}
}
async requestPermission() {
try {
const permission = await DeviceOrientationEvent.requestPermission();
if (permission === 'granted') {
window.addEventListener('deviceorientation', (event) => {
this.handleOrientation(event);
});
}
} catch (error) {
console.error('Permission denied:', error);
}
}
handleOrientation(event) {
// 更新 3D 视角
this.rotation.x = event.beta || 0;
this.rotation.y = event.gamma || 0;
this.rotation.z = event.alpha || 0;
this.updateView();
}
updateView() {
// 应用 CSS 3D 变换
this.element.style.transform = `
rotateX(${this.rotation.x}deg)
rotateY(${this.rotation.y}deg)
rotateZ(${this.rotation.z}deg)
`;
}
}
三、Screen Orientation API
3.1 获取屏幕方向
// 获取屏幕方向
if ('orientation' in screen) {
console.log('Orientation type:', screen.orientation.type);
// 'portrait-primary' - 竖屏主方向
// 'portrait-secondary' - 竖屏次方向
// 'landscape-primary' - 横屏主方向
// 'landscape-secondary' - 横屏次方向
}
// 使用 matchMedia 检测
const isLandscape = window.matchMedia('(orientation: landscape)');
console.log('Is landscape:', isLandscape.matches);
// 监听方向变化
screen.orientation.addEventListener('change', () => {
console.log('New orientation:', screen.orientation.type);
});
3.2 锁定屏幕方向
// 锁定为竖屏
async function lockPortrait() {
try {
await screen.orientation.lock('portrait');
console.log('Locked to portrait');
} catch (error) {
console.error('Lock failed:', error);
}
}
// 锁定为横屏
async function lockLandscape() {
try {
await screen.orientation.lock('landscape');
console.log('Locked to landscape');
} catch (error) {
console.error('Lock failed:', error);
}
}
// 解锁
function unlock() {
screen.orientation.unlock();
}
四、Battery Status API
4.1 获取电池信息
// 获取电池信息
async function getBatteryInfo() {
const battery = await navigator.getBattery();
console.log('Charging:', battery.charging);
console.log('Level:', battery.level * 100 + '%');
console.log('Charging time:', battery.chargingTime, 'seconds');
console.log('Discharging time:', battery.dischargingTime, 'seconds');
}
// 添加事件监听
battery.addEventListener('chargingchange', () => {
console.log('Charging changed:', battery.charging);
});
battery.addEventListener('levelchange', () => {
console.log('Level changed:', battery.level);
});
battery.addEventListener('chargingtimechange', () => {
console.log('Charging time changed');
});
battery.addEventListener('dischargingtimechange', () => {
console.log('Discharging time changed');
});
4.2 实际应用
class BatteryAwarePlayer {
constructor(video) {
this.video = video;
this.lowBatteryThreshold = 0.2; // 20%
this.init();
}
async init() {
const battery = await navigator.getBattery();
// 监听电量变化
battery.addEventListener('levelchange', () => {
this.adjustQuality(battery.level);
});
// 监听充电状态
battery.addEventListener('chargingchange', () => {
if (battery.charging) {
this.setHighQuality();
} else {
this.adjustQuality(battery.level);
}
});
}
adjustQuality(level) {
if (level < this.lowBatteryThreshold) {
// 低电量,降低画质
this.video.setPlaybackQuality('low');
} else {
this.video.setPlaybackQuality('auto');
}
}
setHighQuality() {
this.video.setPlaybackQuality('high');
}
}
五、网络状态 API
5.1 Network Information API
// 获取连接信息
if ('connection' in navigator) {
const connection = navigator.connection;
console.log('Effective type:', connection.effectiveType);
// 'slow-2g' - 2G 网络
// '2g' - 2G 网络
// '3g' - 3G 网络
// '4g' - 4G 网络
console.log('Downlink:', connection.downlink, 'Mbps');
console.log('RTT:', connection.rtt, 'ms');
console.log('Save data:', connection.saveData);
}
// 监听连接变化
connection.addEventListener('change', () => {
console.log('Connection changed');
console.log('New type:', connection.effectiveType);
});
5.2 实际应用
class NetworkAwareMedia {
constructor(video) {
this.video = video;
this.init();
}
init() {
const connection = navigator.connection;
if (!connection) return;
// 根据网络质量调整媒体质量
connection.addEventListener('change', () => {
this.adaptQuality();
});
// 初始调整
this.adaptQuality();
}
adaptQuality() {
const connection = navigator.connection;
const effectiveType = connection.effectiveType;
switch (effectiveType) {
case 'slow-2g':
case '2g':
this.video.setPlaybackRate(0.5);
this.video.setResolution('low');
break;
case '3g':
this.video.setPlaybackRate(1);
this.video.setResolution('medium');
break;
case '4g':
this.video.setPlaybackRate(1);
this.video.setResolution('high');
break;
}
}
}
六、综合示例:智能资源管理
class SmartResourceManager {
constructor() {
this.isVisible = true;
this.isOnBattery = false;
this.isSlowNetwork = false;
this.init();
}
async init() {
// 页面可见性
document.addEventListener('visibilitychange', () => {
this.isVisible = document.visibilityState === 'visible';
this.updateBehavior();
});
// 电池状态
if ('getBattery' in navigator) {
const battery = await navigator.getBattery();
this.isOnBattery = !battery.charging;
battery.addEventListener('chargingchange', () => {
this.isOnBattery = !battery.charging;
this.updateBehavior();
});
}
// 网络状态
if ('connection' in navigator) {
const connection = navigator.connection;
this.isSlowNetwork = ['slow-2g', '2g'].includes(connection.effectiveType);
connection.addEventListener('change', () => {
this.isSlowNetwork = ['slow-2g', '2g'].includes(connection.effectiveType);
this.updateBehavior();
});
}
}
updateBehavior() {
// 决定是否加载高清图片
const loadHighResImages = this.isVisible &&
!this.isOnBattery &&
!this.isSlowNetwork;
// 决定是否自动播放视频
const autoplayVideo = this.isVisible &&
!this.isOnBattery &&
!this.isSlowNetwork;
// 决定轮询频率
const pollingInterval = this.isVisible ? 5000 : 60000;
console.log({
loadHighResImages,
autoplayVideo,
pollingInterval
});
// 触发自定义事件
window.dispatchEvent(new CustomEvent('resource-policy-change', {
detail: { loadHighResImages, autoplayVideo, pollingInterval }
}));
}
}
// 使用
const manager = new SmartResourceManager();