Web API交互-用户体验增强
# Web API交互-用户体验增强
现代浏览器提供了丰富的API来增强用户体验。本文将介绍全屏API、通知API、振动API、地理定位API等实用技术,帮助开发者打造更好的交互体验。
# 一、全屏API
全屏API允许网页以全屏模式显示内容,常用于视频播放、游戏、演示等场景。
# 1.1 基本使用
const element = document.getElementById('video-container');
// 请求全屏
function requestFullscreen() {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullscreen) { // Safari
element.webkitRequestFullscreen();
} else if (element.mozRequestFullScreen) { // Firefox
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) { // IE11
element.msRequestFullscreen();
}
}
// 退出全屏
function exitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
// 切换全屏
function toggleFullscreen() {
if (isFullscreen()) {
exitFullscreen();
} else {
requestFullscreen();
}
}
// 检查是否全屏
function isFullscreen() {
return !!(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
// 获取全屏元素
function getFullscreenElement() {
return (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
# 1.2 监听全屏事件
// 全屏状态改变
document.addEventListener('fullscreenchange', function() {
if (isFullscreen()) {
console.log('进入全屏模式');
updateUI('fullscreen');
} else {
console.log('退出全屏模式');
updateUI('normal');
}
});
// 兼容性写法
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
function handleFullscreenChange() {
const fullscreenElement = getFullscreenElement();
if (fullscreenElement) {
console.log('全屏元素:', fullscreenElement);
}
}
// 全屏错误
document.addEventListener('fullscreenerror', function(event) {
console.error('全屏请求失败');
});
# 1.3 全屏样式
/* 全屏时的样式 */
:fullscreen {
background-color: black;
}
:-webkit-full-screen {
background-color: black;
}
:-moz-full-screen {
background-color: black;
}
:-ms-fullscreen {
background-color: black;
}
/* 全屏元素的子元素样式 */
:fullscreen video {
width: 100%;
height: 100%;
}
# 1.4 实际应用
// 视频播放器全屏
class VideoPlayer {
constructor(videoElement) {
this.video = videoElement;
this.container = videoElement.parentElement;
this.fullscreenBtn = this.container.querySelector('.fullscreen-btn');
this.init();
}
init() {
this.fullscreenBtn.addEventListener('click', () => {
this.toggleFullscreen();
});
document.addEventListener('fullscreenchange', () => {
this.updateFullscreenButton();
});
// ESC键退出全屏
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isFullscreen()) {
this.exitFullscreen();
}
});
}
toggleFullscreen() {
if (this.isFullscreen()) {
this.exitFullscreen();
} else {
this.requestFullscreen();
}
}
requestFullscreen() {
const elem = this.container;
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
}
}
exitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
isFullscreen() {
return !!(document.fullscreenElement || document.webkitFullscreenElement);
}
updateFullscreenButton() {
if (this.isFullscreen()) {
this.fullscreenBtn.textContent = '退出全屏';
} else {
this.fullscreenBtn.textContent = '全屏';
}
}
}
# 二、通知API
通知API允许网页向用户显示系统通知,即使页面不在前台也能提醒用户。
# 2.1 请求权限
// 检查浏览器支持
if (!('Notification' in window)) {
console.log('浏览器不支持通知API');
}
// 检查权限状态
console.log(Notification.permission);
// "default" - 用户未做选择
// "granted" - 用户已授权
// "denied" - 用户已拒绝
// 请求权限
async function requestNotificationPermission() {
if (Notification.permission === 'granted') {
console.log('已有通知权限');
return true;
}
if (Notification.permission === 'denied') {
console.log('用户已拒绝通知权限');
return false;
}
// 请求权限
const permission = await Notification.requestPermission();
if (permission === 'granted') {
console.log('用户授予通知权限');
return true;
} else {
console.log('用户拒绝通知权限');
return false;
}
}
# 2.2 显示通知
// 简单通知
function showSimpleNotification() {
if (Notification.permission === 'granted') {
new Notification('Hello!');
}
}
// 带选项的通知
function showNotification() {
if (Notification.permission !== 'granted') {
console.log('没有通知权限');
return;
}
const notification = new Notification('新消息', {
body: '您有一条新消息待查看',
icon: '/icon.png',
badge: '/badge.png',
image: '/image.png',
tag: 'message-123', // 相同tag会替换旧通知
renotify: false, // 相同tag时是否重新通知
requireInteraction: false, // 是否保持显示直到用户交互
silent: false, // 是否静音
vibrate: [200, 100, 200], // 振动模式
data: { url: '/messages/123' }, // 自定义数据
actions: [ // 操作按钮(部分浏览器不支持)
{ action: 'view', title: '查看' },
{ action: 'close', title: '关闭' }
]
});
// 通知显示
notification.addEventListener('show', () => {
console.log('通知已显示');
});
// 通知点击
notification.addEventListener('click', (event) => {
console.log('通知被点击');
window.focus();
window.location.href = event.target.data.url;
notification.close();
});
// 通知关闭
notification.addEventListener('close', () => {
console.log('通知已关闭');
});
// 通知错误
notification.addEventListener('error', () => {
console.error('通知显示失败');
});
// 3秒后自动关闭
setTimeout(() => {
notification.close();
}, 3000);
}
# 2.3 通知管理
class NotificationManager {
constructor() {
this.notifications = new Map();
}
async init() {
const granted = await this.requestPermission();
return granted;
}
async requestPermission() {
if (!('Notification' in window)) {
return false;
}
if (Notification.permission === 'granted') {
return true;
}
const permission = await Notification.requestPermission();
return permission === 'granted';
}
show(id, title, options = {}) {
if (Notification.permission !== 'granted') {
console.log('没有通知权限');
return null;
}
// 关闭相同ID的旧通知
this.close(id);
const notification = new Notification(title, {
tag: id,
...options
});
this.notifications.set(id, notification);
notification.addEventListener('click', () => {
window.focus();
if (options.onClick) {
options.onClick(notification);
}
this.close(id);
});
notification.addEventListener('close', () => {
this.notifications.delete(id);
if (options.onClose) {
options.onClose();
}
});
return notification;
}
close(id) {
const notification = this.notifications.get(id);
if (notification) {
notification.close();
this.notifications.delete(id);
}
}
closeAll() {
this.notifications.forEach(notification => {
notification.close();
});
this.notifications.clear();
}
}
// 使用示例
const notificationManager = new NotificationManager();
await notificationManager.init();
notificationManager.show('msg-1', '新消息', {
body: '您有一条新消息',
icon: '/icon.png',
onClick: () => {
console.log('通知被点击');
window.location.href = '/messages';
}
});
# 2.4 实际应用场景
// 1. 新消息提醒
function notifyNewMessage(message) {
notificationManager.show(`msg-${message.id}`, '新消息', {
body: `${message.sender}: ${message.content}`,
icon: message.avatar,
onClick: () => {
openChatWindow(message.sender);
}
});
}
// 2. 定时提醒
function setReminder(text, time) {
const delay = time - Date.now();
setTimeout(() => {
notificationManager.show('reminder', '提醒', {
body: text,
requireInteraction: true,
vibrate: [200, 100, 200]
});
}, delay);
}
// 3. 后台任务完成通知
async function processLargeFile(file) {
const result = await uploadFile(file);
notificationManager.show('upload', '上传完成', {
body: `文件 ${file.name} 上传成功`,
icon: '/success-icon.png',
onClick: () => {
window.location.href = result.url;
}
});
}
# 三、振动API
振动API允许网页触发设备振动,常用于移动设备的触觉反馈。
# 3.1 基本使用
// 检查支持
if ('vibrate' in navigator) {
console.log('支持振动API');
} else {
console.log('不支持振动API');
}
// 振动200毫秒
navigator.vibrate(200);
// 或使用数组
navigator.vibrate([200]);
// 振动模式:振动-暂停-振动
navigator.vibrate([100, 50, 100]);
// 振动100ms, 暂停50ms, 振动100ms
// 复杂振动模式
navigator.vibrate([200, 100, 200, 100, 200]);
// 停止振动
navigator.vibrate(0);
navigator.vibrate([]);
# 3.2 实际应用
// 1. 按钮点击反馈
button.addEventListener('click', () => {
navigator.vibrate(10); // 轻微振动
});
// 2. 表单验证失败
function showError(message) {
navigator.vibrate([50, 50, 50]); // 三次短振动
alert(message);
}
// 3. 游戏碰撞反馈
function onCollision() {
navigator.vibrate(100); // 碰撞时振动
}
// 4. 通知提醒
function showNotificationWithVibration(title, body) {
navigator.vibrate([200, 100, 200]);
new Notification(title, {
body: body,
vibrate: [200, 100, 200]
});
}
// 5. 长按反馈
let longPressTimer;
element.addEventListener('touchstart', () => {
longPressTimer = setTimeout(() => {
navigator.vibrate(50); // 长按成功振动
onLongPress();
}, 500);
});
element.addEventListener('touchend', () => {
clearTimeout(longPressTimer);
});
# 四、地理定位API
地理定位API允许网页获取用户的地理位置信息。
# 4.1 基本使用
// 检查支持
if ('geolocation' in navigator) {
console.log('支持地理定位');
} else {
console.log('不支持地理定位');
}
// 获取当前位置(一次性)
navigator.geolocation.getCurrentPosition(
// 成功回调
(position) => {
console.log('纬度:', position.coords.latitude);
console.log('经度:', position.coords.longitude);
console.log('精度:', position.coords.accuracy, '米');
console.log('海拔:', position.coords.altitude);
console.log('海拔精度:', position.coords.altitudeAccuracy);
console.log('方向:', position.coords.heading);
console.log('速度:', position.coords.speed);
console.log('时间戳:', position.timestamp);
},
// 错误回调
(error) => {
switch(error.code) {
case error.PERMISSION_DENIED:
console.error('用户拒绝地理定位请求');
break;
case error.POSITION_UNAVAILABLE:
console.error('位置信息不可用');
break;
case error.TIMEOUT:
console.error('请求超时');
break;
default:
console.error('未知错误');
}
},
// 选项
{
enableHighAccuracy: true, // 高精度(耗电)
timeout: 5000, // 超时时间(毫秒)
maximumAge: 0 // 缓存时间(0表示不使用缓存)
}
);
# 4.2 监听位置变化
// 持续监听位置
const watchId = navigator.geolocation.watchPosition(
(position) => {
console.log('位置更新:', position.coords.latitude, position.coords.longitude);
updateMap(position.coords);
},
(error) => {
console.error('位置获取失败:', error.message);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 5000
}
);
// 停止监听
navigator.geolocation.clearWatch(watchId);
# 4.3 封装地理定位类
class GeoLocation {
constructor(options = {}) {
this.options = {
enableHighAccuracy: false,
timeout: 10000,
maximumAge: 0,
...options
};
this.watchId = null;
}
// 检查支持
isSupported() {
return 'geolocation' in navigator;
}
// 获取当前位置
getCurrentPosition() {
return new Promise((resolve, reject) => {
if (!this.isSupported()) {
reject(new Error('浏览器不支持地理定位'));
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude,
heading: position.coords.heading,
speed: position.coords.speed,
timestamp: position.timestamp
});
},
(error) => {
reject(this.handleError(error));
},
this.options
);
});
}
// 监听位置变化
watchPosition(callback, errorCallback) {
if (!this.isSupported()) {
if (errorCallback) {
errorCallback(new Error('浏览器不支持地理定位'));
}
return null;
}
this.watchId = navigator.geolocation.watchPosition(
(position) => {
callback({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: position.timestamp
});
},
(error) => {
if (errorCallback) {
errorCallback(this.handleError(error));
}
},
this.options
);
return this.watchId;
}
// 停止监听
clearWatch() {
if (this.watchId !== null) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
}
}
// 计算两点距离(米)
calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371e3; // 地球半径(米)
const φ1 = lat1 * Math.PI / 180;
const φ2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
// 错误处理
handleError(error) {
const errorMessages = {
1: '用户拒绝地理定位请求',
2: '位置信息不可用',
3: '请求超时'
};
return new Error(errorMessages[error.code] || '未知错误');
}
}
// 使用示例
const geoLocation = new GeoLocation({
enableHighAccuracy: true,
timeout: 10000
});
// 获取当前位置
try {
const position = await geoLocation.getCurrentPosition();
console.log('当前位置:', position.latitude, position.longitude);
} catch (error) {
console.error('获取位置失败:', error.message);
}
// 监听位置变化
geoLocation.watchPosition(
(position) => {
console.log('位置更新:', position);
},
(error) => {
console.error('位置获取失败:', error.message);
}
);
# 4.4 实际应用
// 1. 附近搜索
async function searchNearby(keyword) {
try {
const position = await geoLocation.getCurrentPosition();
const response = await fetch(`/api/search?keyword=${keyword}&lat=${position.latitude}&lng=${position.longitude}`);
const results = await response.json();
displayResults(results);
} catch (error) {
alert('无法获取位置信息');
}
}
// 2. 导航
async function startNavigation(destination) {
const position = await geoLocation.getCurrentPosition();
// 监听位置变化
geoLocation.watchPosition((newPosition) => {
const distance = geoLocation.calculateDistance(
newPosition.latitude,
newPosition.longitude,
destination.lat,
destination.lng
);
if (distance < 10) {
alert('已到达目的地');
geoLocation.clearWatch();
}
});
}
// 3. 签到
async function checkIn() {
try {
const position = await geoLocation.getCurrentPosition();
const response = await fetch('/api/checkin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
latitude: position.latitude,
longitude: position.longitude,
accuracy: position.accuracy
})
});
const result = await response.json();
alert('签到成功');
} catch (error) {
alert('签到失败');
}
}
# 五、综合示例
# 六、最佳实践
# 6.1 全屏API
- 提供明显的全屏按钮
- 支持ESC键退出全屏
- 全屏状态UI适配
- 处理全屏失败情况
- 记住用户全屏偏好
# 6.2 通知API
- 在合适时机请求权限(不要页面加载时就请求)
- 提供清晰的权限说明
- 使用有意义的通知内容
- 设置合理的通知时长
- 相同类型通知使用相同tag避免堆积
# 6.3 振动API
- 振动时长不宜过长(影响用户体验)
- 提供关闭振动的选项
- 注意设备兼容性(主要是移动设备)
- 不要过度使用振动
# 6.4 地理定位API
- 说明为何需要位置信息
- 提供位置权限拒绝的降级方案
- 合理设置精度要求(高精度耗电)
- 使用缓存减少定位请求
- 处理定位失败情况
# 七、总结
用户体验增强API提供了丰富的交互能力:
- 全屏API:沉浸式体验,适合视频、游戏
- 通知API:主动提醒,增强用户参与
- 振动API:触觉反馈,提升交互体验
- 地理定位API:位置服务,支持本地化功能
合理使用这些API,能够显著提升用户体验和应用价值。
祝你变得更强!
编辑 (opens new window)
上次更新: 2025/11/28