📚 学习目标
- 掌握HTML DOM API的后9个高级接口
- 理解现代Web开发中的复杂应用场景
- 学会构建高性能、可扩展的Web应用
- 掌握前沿技术的实际应用方法
🎯 难度等级
高级 - 适合有扎实JavaScript基础和Web开发经验的开发者
🏷️ 技术标签
JavaScript DOM API Web开发 性能优化 现代前端
⏱️ 阅读时间
约18-25分钟
🚀 引言
在上篇文章中,我们深入探讨了HTML DOM API的前9个核心接口。本篇将继续这一技术之旅,重点介绍更加高级和专业的API接口,这些接口是构建现代Web应用不可或缺的技术基础。
从多线程处理到实时通信,从3D图形渲染到音频处理,这些高级API将帮助你构建更加强大和用户友好的Web应用。
🎯 高级API详解
10. Web Worker接口:多线程处理的利器
🔍 应用场景
大数据处理、复杂计算、图像处理、后台任务
❌ 常见问题
javascript
// ❌ 主线程执行耗时操作,阻塞UI
function processLargeData(data) {
let result = [];
for (let i = 0; i < data.length; i++) {
// 复杂计算
result.push(heavyComputation(data[i]));
}
return result;
}
// UI会被阻塞
const result = processLargeData(largeDataSet);
updateUI(result);
✅ 推荐方案
javascript
// ✅ Web Worker管理器
class WorkerManager {
constructor() {
this.workers = new Map();
this.taskQueue = [];
this.maxWorkers = navigator.hardwareConcurrency || 4;
}
/**
* 创建Worker
* @param {string} name - Worker名称
* @param {string} scriptPath - Worker脚本路径
* @returns {Promise<Worker>}
*/
async createWorker(name, scriptPath) {
try {
const worker = new Worker(scriptPath);
// 设置错误处理
worker.onerror = (error) => {
console.error(`Worker ${name} error:`, error);
this.removeWorker(name);
};
// 设置消息处理
worker.onmessage = (event) => {
this.handleWorkerMessage(name, event);
};
this.workers.set(name, {
worker,
busy: false,
tasks: new Map()
});
return worker;
} catch (error) {
console.error('Failed to create worker:', error);
throw error;
}
}
/**
* 执行任务
* @param {string} workerName - Worker名称
* @param {string} taskType - 任务类型
* @param {*} data - 任务数据
* @returns {Promise}
*/
async executeTask(workerName, taskType, data) {
const workerInfo = this.workers.get(workerName);
if (!workerInfo) {
throw new Error(`Worker ${workerName} not found`);
}
const taskId = this.generateTaskId();
return new Promise((resolve, reject) => {
// 存储任务回调
workerInfo.tasks.set(taskId, { resolve, reject });
// 发送任务到Worker
workerInfo.worker.postMessage({
taskId,
type: taskType,
data
});
workerInfo.busy = true;
});
}
/**
* 处理Worker消息
* @param {string} workerName - Worker名称
* @param {MessageEvent} event - 消息事件
*/
handleWorkerMessage(workerName, event) {
const { taskId, result, error } = event.data;
const workerInfo = this.workers.get(workerName);
if (!workerInfo) return;
const task = workerInfo.tasks.get(taskId);
if (!task) return;
// 清理任务
workerInfo.tasks.delete(taskId);
workerInfo.busy = false;
// 执行回调
if (error) {
task.reject(new Error(error));
} else {
task.resolve(result);
}
}
/**
* 批量处理任务
* @param {Array} tasks - 任务数组
* @param {string} workerScript - Worker脚本
* @returns {Promise<Array>}
*/
async processBatch(tasks, workerScript) {
const results = [];
const workers = [];
// 创建Worker池
for (let i = 0; i < Math.min(this.maxWorkers, tasks.length); i++) {
const workerName = `batch-worker-${i}`;
await this.createWorker(workerName, workerScript);
workers.push(workerName);
}
// 分配任务
const promises = tasks.map((task, index) => {
const workerName = workers[index % workers.length];
return this.executeTask(workerName, task.type, task.data);
});
try {
const results = await Promise.all(promises);
return results;
} finally {
// 清理Worker
workers.forEach(name => this.removeWorker(name));
}
}
/**
* 移除Worker
* @param {string} name - Worker名称
*/
removeWorker(name) {
const workerInfo = this.workers.get(name);
if (workerInfo) {
workerInfo.worker.terminate();
this.workers.delete(name);
}
}
/**
* 生成任务ID
* @returns {string}
*/
generateTaskId() {
return `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 清理所有Worker
*/
cleanup() {
for (const [name] of this.workers) {
this.removeWorker(name);
}
}
}
// Worker脚本示例 (data-processor.js)
const workerScript = `
self.onmessage = function(event) {
const { taskId, type, data } = event.data;
try {
let result;
switch (type) {
case 'processData':
result = processLargeDataSet(data);
break;
case 'imageFilter':
result = applyImageFilter(data);
break;
case 'calculation':
result = performComplexCalculation(data);
break;
default:
throw new Error('Unknown task type: ' + type);
}
self.postMessage({ taskId, result });
} catch (error) {
self.postMessage({ taskId, error: error.message });
}
};
function processLargeDataSet(data) {
return data.map(item => {
// 复杂数据处理逻辑
return {
...item,
processed: true,
timestamp: Date.now()
};
});
}
function applyImageFilter(imageData) {
const { data: pixels, width, height } = imageData;
// 应用灰度滤镜
for (let i = 0; i < pixels.length; i += 4) {
const gray = pixels[i] * 0.299 + pixels[i + 1] * 0.587 + pixels[i + 2] * 0.114;
pixels[i] = gray; // Red
pixels[i + 1] = gray; // Green
pixels[i + 2] = gray; // Blue
// Alpha channel (i + 3) remains unchanged
}
return { data: pixels, width, height };
}
function performComplexCalculation(numbers) {
return numbers.reduce((acc, num) => {
// 模拟复杂计算
for (let i = 0; i < 1000000; i++) {
acc += Math.sqrt(num * i);
}
return acc;
}, 0);
}
`;
// 使用示例
const workerManager = new WorkerManager();
// 创建数据处理Worker
await workerManager.createWorker('dataProcessor', 'data-processor.js');
// 处理大数据集
const largeData = Array.from({ length: 10000 }, (_, i) => ({ id: i, value: Math.random() }));
try {
const result = await workerManager.executeTask('dataProcessor', 'processData', largeData);
console.log('数据处理完成:', result);
updateUI(result);
} catch (error) {
console.error('数据处理失败:', error);
}
// 批量处理任务
const tasks = [
{ type: 'calculation', data: [1, 2, 3, 4, 5] },
{ type: 'calculation', data: [6, 7, 8, 9, 10] },
{ type: 'calculation', data: [11, 12, 13, 14, 15] }
];
const batchResults = await workerManager.processBatch(tasks, 'data-processor.js');
console.log('批量处理结果:', batchResults);
11. WebRTC接口:实时通信的核心
🔍 应用场景
视频通话、音频聊天、屏幕共享、P2P数据传输
❌ 常见问题
javascript
// ❌ 简单的WebRTC实现,缺乏错误处理和连接管理
const pc = new RTCPeerConnection();
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
pc.addStream(stream);
});
✅ 推荐方案
javascript
// ✅ 专业的WebRTC通信管理器
class WebRTCManager {
constructor(options = {}) {
this.options = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
],
...options
};
this.localStream = null;
this.remoteStream = null;
this.peerConnection = null;
this.dataChannel = null;
this.isInitiator = false;
this.listeners = new Map();
}
/**
* 初始化WebRTC连接
* @returns {Promise}
*/
async initialize() {
try {
this.peerConnection = new RTCPeerConnection({
iceServers: this.options.iceServers
});
this.setupPeerConnectionEvents();
return true;
} catch (error) {
console.error('WebRTC初始化失败:', error);
throw error;
}
}
/**
* 设置PeerConnection事件监听
*/
setupPeerConnectionEvents() {
// ICE候选事件
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.emit('iceCandidate', event.candidate);
}
};
// 连接状态变化
this.peerConnection.onconnectionstatechange = () => {
const state = this.peerConnection.connectionState;
this.emit('connectionStateChange', state);
if (state === 'failed') {
this.handleConnectionFailure();
}
};
// 远程流接收
this.peerConnection.ontrack = (event) => {
this.remoteStream = event.streams[0];
this.emit('remoteStream', this.remoteStream);
};
// 数据通道接收
this.peerConnection.ondatachannel = (event) => {
const channel = event.channel;
this.setupDataChannelEvents(channel);
this.emit('dataChannel', channel);
};
}
/**
* 获取用户媒体
* @param {Object} constraints - 媒体约束
* @returns {Promise<MediaStream>}
*/
async getUserMedia(constraints = { video: true, audio: true }) {
try {
this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
// 添加轨道到PeerConnection
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream);
});
this.emit('localStream', this.localStream);
return this.localStream;
} catch (error) {
console.error('获取用户媒体失败:', error);
throw error;
}
}
/**
* 获取屏幕共享
* @returns {Promise<MediaStream>}
*/
async getDisplayMedia() {
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true
});
// 替换视频轨道
const videoTrack = screenStream.getVideoTracks()[0];
const sender = this.peerConnection.getSenders().find(s =>
s.track && s.track.kind === 'video'
);
if (sender) {
await sender.replaceTrack(videoTrack);
}
// 监听屏幕共享结束
videoTrack.onended = () => {
this.stopScreenShare();
};
this.emit('screenShare', screenStream);
return screenStream;
} catch (error) {
console.error('获取屏幕共享失败:', error);
throw error;
}
}
/**
* 停止屏幕共享
*/
async stopScreenShare() {
try {
// 恢复摄像头
const videoTrack = this.localStream.getVideoTracks()[0];
const sender = this.peerConnection.getSenders().find(s =>
s.track && s.track.kind === 'video'
);
if (sender && videoTrack) {
await sender.replaceTrack(videoTrack);
}
this.emit('screenShareStopped');
} catch (error) {
console.error('停止屏幕共享失败:', error);
}
}
/**
* 创建Offer
* @returns {Promise<RTCSessionDescription>}
*/
async createOffer() {
try {
this.isInitiator = true;
// 创建数据通道
this.dataChannel = this.peerConnection.createDataChannel('messages', {
ordered: true
});
this.setupDataChannelEvents(this.dataChannel);
const offer = await this.peerConnection.createOffer();
await this.peerConnection.setLocalDescription(offer);
return offer;
} catch (error) {
console.error('创建Offer失败:', error);
throw error;
}
}
/**
* 创建Answer
* @param {RTCSessionDescription} offer - 远程Offer
* @returns {Promise<RTCSessionDescription>}
*/
async createAnswer(offer) {
try {
await this.peerConnection.setRemoteDescription(offer);
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
return answer;
} catch (error) {
console.error('创建Answer失败:', error);
throw error;
}
}
/**
* 设置远程描述
* @param {RTCSessionDescription} answer - 远程Answer
*/
async setRemoteAnswer(answer) {
try {
await this.peerConnection.setRemoteDescription(answer);
} catch (error) {
console.error('设置远程Answer失败:', error);
throw error;
}
}
/**
* 添加ICE候选
* @param {RTCIceCandidate} candidate - ICE候选
*/
async addIceCandidate(candidate) {
try {
await this.peerConnection.addIceCandidate(candidate);
} catch (error) {
console.error('添加ICE候选失败:', error);
}
}
/**
* 设置数据通道事件
* @param {RTCDataChannel} channel - 数据通道
*/
setupDataChannelEvents(channel) {
channel.onopen = () => {
this.emit('dataChannelOpen', channel);
};
channel.onmessage = (event) => {
this.emit('dataChannelMessage', event.data);
};
channel.onclose = () => {
this.emit('dataChannelClose');
};
channel.onerror = (error) => {
console.error('数据通道错误:', error);
this.emit('dataChannelError', error);
};
}
/**
* 发送数据
* @param {*} data - 要发送的数据
*/
sendData(data) {
if (this.dataChannel && this.dataChannel.readyState === 'open') {
const message = typeof data === 'string' ? data : JSON.stringify(data);
this.dataChannel.send(message);
} else {
console.warn('数据通道未打开');
}
}
/**
* 处理连接失败
*/
async handleConnectionFailure() {
console.log('连接失败,尝试重新连接...');
// 重新创建ICE连接
this.peerConnection.restartIce();
this.emit('connectionFailure');
}
/**
* 关闭连接
*/
close() {
// 停止本地流
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
}
// 关闭数据通道
if (this.dataChannel) {
this.dataChannel.close();
}
// 关闭PeerConnection
if (this.peerConnection) {
this.peerConnection.close();
}
this.emit('closed');
}
/**
* 事件监听
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
/**
* 触发事件
* @param {string} event - 事件名
* @param {*} data - 事件数据
*/
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(callback => callback(data));
}
}
// 使用示例
const rtcManager = new WebRTCManager();
// 初始化WebRTC
await rtcManager.initialize();
// 监听事件
rtcManager.on('localStream', (stream) => {
document.getElementById('localVideo').srcObject = stream;
});
rtcManager.on('remoteStream', (stream) => {
document.getElementById('remoteVideo').srcObject = stream;
});
rtcManager.on('dataChannelMessage', (message) => {
console.log('收到消息:', message);
});
// 发起通话
document.getElementById('startCall').addEventListener('click', async () => {
await rtcManager.getUserMedia();
const offer = await rtcManager.createOffer();
// 通过信令服务器发送offer
sendToSignalingServer({ type: 'offer', offer });
});
// 接听通话
document.getElementById('answerCall').addEventListener('click', async () => {
await rtcManager.getUserMedia();
// 假设从信令服务器收到offer
const answer = await rtcManager.createAnswer(receivedOffer);
// 通过信令服务器发送answer
sendToSignalingServer({ type: 'answer', answer });
});
12. WebGL接口:3D图形渲染的强大工具
🔍 应用场景
3D游戏、数据可视化、CAD应用、虚拟现实
❌ 常见问题
javascript
// ❌ 直接使用WebGL API,代码复杂难维护
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
✅ 推荐方案
javascript
// ✅ WebGL渲染引擎
class WebGLRenderer {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.gl = this.initWebGL();
this.programs = new Map();
this.buffers = new Map();
this.textures = new Map();
this.uniforms = new Map();
this.options = {
clearColor: [0.0, 0.0, 0.0, 1.0],
enableDepthTest: true,
...options
};
this.setupWebGL();
}
/**
* 初始化WebGL上下文
* @returns {WebGLRenderingContext}
*/
initWebGL() {
const gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
if (!gl) {
throw new Error('WebGL not supported');
}
return gl;
}
/**
* 设置WebGL基本配置
*/
setupWebGL() {
const { gl, options } = this;
// 设置清除颜色
gl.clearColor(...options.clearColor);
// 启用深度测试
if (options.enableDepthTest) {
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
}
// 设置视口
this.resize();
}
/**
* 创建着色器
* @param {string} source - 着色器源码
* @param {number} type - 着色器类型
* @returns {WebGLShader}
*/
createShader(source, type) {
const { gl } = this;
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const error = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error(`Shader compilation error: ${error}`);
}
return shader;
}
/**
* 创建着色器程序
* @param {string} vertexSource - 顶点着色器源码
* @param {string} fragmentSource - 片段着色器源码
* @param {string} name - 程序名称
* @returns {WebGLProgram}
*/
createProgram(vertexSource, fragmentSource, name) {
const { gl } = this;
const vertexShader = this.createShader(vertexSource, gl.VERTEX_SHADER);
const fragmentShader = this.createShader(fragmentSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const error = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(`Program linking error: ${error}`);
}
// 清理着色器
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
// 存储程序
this.programs.set(name, program);
return program;
}
/**
* 创建缓冲区
* @param {ArrayBuffer|Float32Array} data - 数据
* @param {number} type - 缓冲区类型
* @param {number} usage - 使用方式
* @param {string} name - 缓冲区名称
* @returns {WebGLBuffer}
*/
createBuffer(data, type = this.gl.ARRAY_BUFFER, usage = this.gl.STATIC_DRAW, name) {
const { gl } = this;
const buffer = gl.createBuffer();
gl.bindBuffer(type, buffer);
gl.bufferData(type, data, usage);
if (name) {
this.buffers.set(name, { buffer, type, size: data.length });
}
return buffer;
}
/**
* 创建纹理
* @param {HTMLImageElement|HTMLCanvasElement} image - 图像源
* @param {string} name - 纹理名称
* @returns {WebGLTexture}
*/
createTexture(image, name) {
const { gl } = this;
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// 设置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// 上传纹理数据
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
if (name) {
this.textures.set(name, texture);
}
return texture;
}
/**
* 设置uniform变量
* @param {WebGLProgram} program - 着色器程序
* @param {string} name - uniform名称
* @param {*} value - 值
*/
setUniform(program, name, value) {
const { gl } = this;
const location = gl.getUniformLocation(program, name);
if (location === null) return;
if (Array.isArray(value)) {
switch (value.length) {
case 1:
gl.uniform1f(location, value[0]);
break;
case 2:
gl.uniform2fv(location, value);
break;
case 3:
gl.uniform3fv(location, value);
break;
case 4:
gl.uniform4fv(location, value);
break;
case 16:
gl.uniformMatrix4fv(location, false, value);
break;
}
} else if (typeof value === 'number') {
gl.uniform1f(location, value);
}
}
/**
* 绑定属性
* @param {WebGLProgram} program - 着色器程序
* @param {string} name - 属性名称
* @param {WebGLBuffer} buffer - 缓冲区
* @param {number} size - 组件数量
* @param {number} type - 数据类型
*/
bindAttribute(program, name, buffer, size = 3, type = this.gl.FLOAT) {
const { gl } = this;
const location = gl.getAttribLocation(program, name);
if (location === -1) return;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(location);
gl.vertexAttribPointer(location, size, type, false, 0, 0);
}
/**
* 渲染场景
* @param {Object} scene - 场景对象
*/
render(scene) {
const { gl } = this;
// 清除画布
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 渲染每个对象
scene.objects.forEach(object => {
this.renderObject(object);
});
}
/**
* 渲染单个对象
* @param {Object} object - 渲染对象
*/
renderObject(object) {
const { gl } = this;
const program = this.programs.get(object.program);
if (!program) return;
// 使用着色器程序
gl.useProgram(program);
// 设置uniforms
Object.entries(object.uniforms || {}).forEach(([name, value]) => {
this.setUniform(program, name, value);
});
// 绑定属性
Object.entries(object.attributes || {}).forEach(([name, attr]) => {
const buffer = this.buffers.get(attr.buffer);
if (buffer) {
this.bindAttribute(program, name, buffer.buffer, attr.size, attr.type);
}
});
// 绑定纹理
if (object.texture) {
const texture = this.textures.get(object.texture);
if (texture) {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
this.setUniform(program, 'u_texture', 0);
}
}
// 绘制
if (object.indices) {
const indexBuffer = this.buffers.get(object.indices);
if (indexBuffer) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);
gl.drawElements(object.mode || gl.TRIANGLES, indexBuffer.size, gl.UNSIGNED_SHORT, 0);
}
} else {
const vertexBuffer = this.buffers.get(object.vertices);
if (vertexBuffer) {
gl.drawArrays(object.mode || gl.TRIANGLES, 0, vertexBuffer.size / 3);
}
}
}
/**
* 调整画布大小
*/
resize() {
const { canvas, gl } = this;
const displayWidth = canvas.clientWidth;
const displayHeight = canvas.clientHeight;
if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
canvas.width = displayWidth;
canvas.height = displayHeight;
gl.viewport(0, 0, displayWidth, displayHeight);
}
}
/**
* 清理资源
*/
cleanup() {
const { gl } = this;
// 删除程序
this.programs.forEach(program => gl.deleteProgram(program));
// 删除缓冲区
this.buffers.forEach(({ buffer }) => gl.deleteBuffer(buffer));
// 删除纹理
this.textures.forEach(texture => gl.deleteTexture(texture));
}
}
// 使用示例
const canvas = document.getElementById('webgl-canvas');
const renderer = new WebGLRenderer(canvas);
// 顶点着色器
const vertexShaderSource = `
attribute vec3 a_position;
attribute vec2 a_texCoord;
uniform mat4 u_modelViewMatrix;
uniform mat4 u_projectionMatrix;
varying vec2 v_texCoord;
void main() {
gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(a_position, 1.0);
v_texCoord = a_texCoord;
}
`;
// 片段着色器
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_texture;
uniform float u_time;
varying vec2 v_texCoord;
void main() {
vec4 color = texture2D(u_texture, v_texCoord);
color.rgb *= 0.5 + 0.5 * sin(u_time);
gl_FragColor = color;
}
`;
// 创建着色器程序
renderer.createProgram(vertexShaderSource, fragmentShaderSource, 'basic');
// 创建立方体顶点数据
const vertices = new Float32Array([
// 前面
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
// 后面
-1, -1, -1,
-1, 1, -1,
1, 1, -1,
1, -1, -1
]);
const indices = new Uint16Array([
0, 1, 2, 0, 2, 3, // 前面
4, 5, 6, 4, 6, 7, // 后面
5, 0, 3, 5, 3, 6, // 左面
1, 4, 7, 1, 7, 2, // 右面
3, 2, 7, 3, 7, 6, // 上面
5, 4, 1, 5, 1, 0 // 下面
]);
// 创建缓冲区
renderer.createBuffer(vertices, renderer.gl.ARRAY_BUFFER, renderer.gl.STATIC_DRAW, 'vertices');
renderer.createBuffer(indices, renderer.gl.ELEMENT_ARRAY_BUFFER, renderer.gl.STATIC_DRAW, 'indices');
// 创建场景
const scene = {
objects: [{
program: 'basic',
vertices: 'vertices',
indices: 'indices',
attributes: {
a_position: { buffer: 'vertices', size: 3 }
},
uniforms: {
u_modelViewMatrix: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,-5,1],
u_projectionMatrix: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1],
u_time: 0
}
}]
};
// 渲染循环
function animate(time) {
scene.objects[0].uniforms.u_time = time * 0.001;
renderer.render(scene);
requestAnimationFrame(animate);
}
animate(0);
13. Web Audio接口:音频处理的专业方案
🔍 应用场景
音频播放器、音效处理、音乐制作、语音识别
❌ 常见问题
javascript
// ❌ 简单的音频播放,功能有限
const audio = new Audio('music.mp3');
audio.play();
audio.volume = 0.5;
✅ 推荐方案
javascript
// ✅ 专业音频处理引擎
class AudioEngine {
constructor() {
this.context = null;
this.masterGain = null;
this.sources = new Map();
this.effects = new Map();
this.isInitialized = false;
}
/**
* 初始化音频上下文
* @returns {Promise}
*/
async initialize() {
try {
// 创建音频上下文
this.context = new (window.AudioContext || window.webkitAudioContext)();
// 创建主音量控制
this.masterGain = this.context.createGain();
this.masterGain.connect(this.context.destination);
// 恢复音频上下文(某些浏览器需要用户交互)
if (this.context.state === 'suspended') {
await this.context.resume();
}
this.isInitialized = true;
console.log('音频引擎初始化成功');
} catch (error) {
console.error('音频引擎初始化失败:', error);
throw error;
}
}
/**
* 加载音频文件
* @param {string} url - 音频文件URL
* @param {string} name - 音频名称
* @returns {Promise<AudioBuffer>}
*/
async loadAudio(url, name) {
try {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await this.context.decodeAudioData(arrayBuffer);
this.sources.set(name, audioBuffer);
return audioBuffer;
} catch (error) {
console.error(`加载音频失败 ${url}:`, error);
throw error;
}
}
/**
* 播放音频
* @param {string} name - 音频名称
* @param {Object} options - 播放选项
* @returns {AudioBufferSourceNode}
*/
playAudio(name, options = {}) {
if (!this.isInitialized) {
console.warn('音频引擎未初始化');
return null;
}
const audioBuffer = this.sources.get(name);
if (!audioBuffer) {
console.warn(`音频 ${name} 未找到`);
return null;
}
const source = this.context.createBufferSource();
source.buffer = audioBuffer;
// 创建音量控制
const gainNode = this.context.createGain();
gainNode.gain.value = options.volume || 1.0;
// 连接音频图
source.connect(gainNode);
gainNode.connect(this.masterGain);
// 设置播放参数
if (options.loop) {
source.loop = true;
}
if (options.playbackRate) {
source.playbackRate.value = options.playbackRate;
}
// 开始播放
const startTime = options.when || this.context.currentTime;
const offset = options.offset || 0;
const duration = options.duration || audioBuffer.duration;
source.start(startTime, offset, duration);
// 设置结束回调
if (options.onEnded) {
source.onended = options.onEnded;
}
return source;
}
/**
* 创建音频效果器
* @param {string} type - 效果器类型
* @param {Object} params - 参数
* @returns {AudioNode}
*/
createEffect(type, params = {}) {
let effect;
switch (type) {
case 'reverb':
effect = this.createReverb(params);
break;
case 'delay':
effect = this.createDelay(params);
break;
case 'filter':
effect = this.createFilter(params);
break;
case 'distortion':
effect = this.createDistortion(params);
break;
case 'compressor':
effect = this.createCompressor(params);
break;
default:
console.warn(`未知效果器类型: ${type}`);
return null;
}
return effect;
}
/**
* 创建混响效果
* @param {Object} params - 混响参数
* @returns {ConvolverNode}
*/
createReverb(params = {}) {
const convolver = this.context.createConvolver();
// 创建冲激响应
const length = params.length || this.context.sampleRate * 2;
const impulse = this.context.createBuffer(2, length, this.context.sampleRate);
for (let channel = 0; channel < 2; channel++) {
const channelData = impulse.getChannelData(channel);
for (let i = 0; i < length; i++) {
const decay = Math.pow(1 - i / length, params.decay || 2);
channelData[i] = (Math.random() * 2 - 1) * decay;
}
}
convolver.buffer = impulse;
return convolver;
}
/**
* 创建延迟效果
* @param {Object} params - 延迟参数
* @returns {Object}
*/
createDelay(params = {}) {
const delay = this.context.createDelay(params.maxDelay || 1.0);
const feedback = this.context.createGain();
const wetGain = this.context.createGain();
const dryGain = this.context.createGain();
const output = this.context.createGain();
// 设置参数
delay.delayTime.value = params.delayTime || 0.3;
feedback.gain.value = params.feedback || 0.3;
wetGain.gain.value = params.wet || 0.5;
dryGain.gain.value = params.dry || 0.5;
// 连接节点
delay.connect(feedback);
feedback.connect(delay);
delay.connect(wetGain);
wetGain.connect(output);
dryGain.connect(output);
return {
input: delay,
output: output,
dryGain: dryGain
};
}
/**
* 创建滤波器
* @param {Object} params - 滤波器参数
* @returns {BiquadFilterNode}
*/
createFilter(params = {}) {
const filter = this.context.createBiquadFilter();
filter.type = params.type || 'lowpass';
filter.frequency.value = params.frequency || 1000;
filter.Q.value = params.Q || 1;
filter.gain.value = params.gain || 0;
return filter;
}
/**
* 创建失真效果
* @param {Object} params - 失真参数
* @returns {WaveShaperNode}
*/
createDistortion(params = {}) {
const waveshaper = this.context.createWaveShaper();
const amount = params.amount || 50;
const samples = 44100;
const curve = new Float32Array(samples);
for (let i = 0; i < samples; i++) {
const x = (i * 2) / samples - 1;
curve[i] = ((3 + amount) * x * 20 * Math.PI / 180) / (Math.PI + amount * Math.abs(x));
}
waveshaper.curve = curve;
waveshaper.oversample = '4x';
return waveshaper;
}
/**
* 创建压缩器
* @param {Object} params - 压缩器参数
* @returns {DynamicsCompressorNode}
*/
createCompressor(params = {}) {
const compressor = this.context.createDynamicsCompressor();
compressor.threshold.value = params.threshold || -24;
compressor.knee.value = params.knee || 30;
compressor.ratio.value = params.ratio || 12;
compressor.attack.value = params.attack || 0.003;
compressor.release.value = params.release || 0.25;
return compressor;
}
/**
* 创建音频分析器
* @param {number} fftSize - FFT大小
* @returns {AnalyserNode}
*/
createAnalyser(fftSize = 2048) {
const analyser = this.context.createAnalyser();
analyser.fftSize = fftSize;
analyser.smoothingTimeConstant = 0.8;
return analyser;
}
/**
* 录制音频
* @param {MediaStream} stream - 媒体流
* @returns {Object}
*/
createRecorder(stream) {
const source = this.context.createMediaStreamSource(stream);
const processor = this.context.createScriptProcessor(4096, 1, 1);
const recordedChunks = [];
let isRecording = false;
processor.onaudioprocess = (event) => {
if (!isRecording) return;
const inputData = event.inputBuffer.getChannelData(0);
const chunk = new Float32Array(inputData);
recordedChunks.push(chunk);
};
source.connect(processor);
processor.connect(this.context.destination);
return {
start: () => {
isRecording = true;
recordedChunks.length = 0;
},
stop: () => {
isRecording = false;
return this.exportRecording(recordedChunks);
},
source: source
};
}
/**
* 导出录音
* @param {Array} chunks - 音频块
* @returns {AudioBuffer}
*/
exportRecording(chunks) {
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const audioBuffer = this.context.createBuffer(1, totalLength, this.context.sampleRate);
const channelData = audioBuffer.getChannelData(0);
let offset = 0;
chunks.forEach(chunk => {
channelData.set(chunk, offset);
offset += chunk.length;
});
return audioBuffer;
}
/**
* 设置主音量
* @param {number} volume - 音量值 (0-1)
*/
setMasterVolume(volume) {
if (this.masterGain) {
this.masterGain.gain.value = Math.max(0, Math.min(1, volume));
}
}
/**
* 获取当前时间
* @returns {number}
*/
getCurrentTime() {
return this.context ? this.context.currentTime : 0;
}
/**
* 暂停音频上下文
*/
suspend() {
if (this.context && this.context.state === 'running') {
return this.context.suspend();
}
}
/**
* 恢复音频上下文
*/
resume() {
if (this.context && this.context.state === 'suspended') {
return this.context.resume();
}
}
}
// 使用示例
const audioEngine = new AudioEngine();
// 初始化音频引擎
await audioEngine.initialize();
// 加载音频文件
await audioEngine.loadAudio('music.mp3', 'bgm');
await audioEngine.loadAudio('click.wav', 'click');
// 播放背景音乐
const bgmSource = audioEngine.playAudio('bgm', {
loop: true,
volume: 0.7
});
// 播放点击音效
document.getElementById('button').addEventListener('click', () => {
audioEngine.playAudio('click', {
volume: 0.5,
playbackRate: 1.2
});
});
// 创建音频效果链
const reverb = audioEngine.createEffect('reverb', {
length: audioEngine.context.sampleRate * 3,
decay: 2
});
const delay = audioEngine.createEffect('delay', {
delayTime: 0.3,
feedback: 0.4,
wet: 0.3
});
// 连接效果链
bgmSource.disconnect();
bgmSource.connect(reverb);
reverb.connect(delay.input);
delay.output.connect(audioEngine.masterGain);
### 14. 触摸手势接口:移动端交互的基础
#### 🔍 应用场景
移动端手势识别、触摸交互、手势控制、多点触控
#### ❌ 常见问题
```javascript
// ❌ 简单的触摸事件处理,功能有限
element.addEventListener('touchstart', (e) => {
console.log('触摸开始');
});
✅ 推荐方案
javascript
// ✅ 专业的触摸手势管理器
class TouchGestureManager {
constructor(element, options = {}) {
this.element = element;
this.options = {
enableTap: true,
enableSwipe: true,
enablePinch: true,
enableRotate: true,
enableLongPress: true,
tapTimeout: 300,
longPressTimeout: 500,
swipeThreshold: 50,
...options
};
this.touches = new Map();
this.gestureState = {
isActive: false,
startTime: 0,
startDistance: 0,
startAngle: 0,
lastScale: 1,
lastRotation: 0
};
this.listeners = new Map();
this.longPressTimer = null;
this.mouseDown = false;
this.init();
}
/**
* 初始化事件监听
*/
init() {
// 触摸事件
this.handleTouchStart = this.handleTouchStart.bind(this);
this.handleTouchMove = this.handleTouchMove.bind(this);
this.handleTouchEnd = this.handleTouchEnd.bind(this);
this.handleTouchCancel = this.handleTouchCancel.bind(this);
this.element.addEventListener('touchstart', this.handleTouchStart, { passive: false });
this.element.addEventListener('touchmove', this.handleTouchMove, { passive: false });
this.element.addEventListener('touchend', this.handleTouchEnd, { passive: false });
this.element.addEventListener('touchcancel', this.handleTouchCancel, { passive: false });
// 鼠标事件(用于桌面端测试)
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
this.element.addEventListener('mousedown', this.handleMouseDown);
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
}
/**
* 处理触摸开始
* @param {TouchEvent} event - 触摸事件
*/
handleTouchStart(event) {
event.preventDefault();
const touches = Array.from(event.touches);
this.updateTouches(touches);
if (touches.length === 1) {
// 单点触摸
const touch = touches[0];
this.gestureState.startX = touch.clientX;
this.gestureState.startY = touch.clientY;
this.gestureState.startTime = Date.now();
// 长按检测
if (this.options.enableLongPress) {
this.longPressTimer = setTimeout(() => {
this.emit('longpress', {
x: touch.clientX,
y: touch.clientY,
touch: touch
});
}, this.options.longPressTimeout);
}
} else if (touches.length === 2) {
// 双点触摸
this.gestureState.isActive = true;
this.gestureState.startDistance = this.getDistance(touches[0], touches[1]);
this.gestureState.startAngle = this.getAngle(touches[0], touches[1]);
this.gestureState.lastScale = 1;
this.gestureState.lastRotation = 0;
// 清除长按定时器
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
}
}
/**
* 处理触摸移动
* @param {TouchEvent} event - 触摸事件
*/
handleTouchMove(event) {
event.preventDefault();
const touches = Array.from(event.touches);
this.updateTouches(touches);
if (touches.length === 2 && this.gestureState.isActive) {
const currentDistance = this.getDistance(touches[0], touches[1]);
const currentAngle = this.getAngle(touches[0], touches[1]);
const center = this.getCenter(touches[0], touches[1]);
// 缩放检测
if (this.options.enablePinch) {
const scale = currentDistance / this.gestureState.startDistance;
const deltaScale = scale / this.gestureState.lastScale;
this.emit('pinch', {
scale: scale,
deltaScale: deltaScale,
center: center,
touches: touches
});
this.gestureState.lastScale = scale;
}
// 旋转检测
if (this.options.enableRotate) {
let rotation = currentAngle - this.gestureState.startAngle;
// 处理角度跨越
if (rotation > 180) rotation -= 360;
if (rotation < -180) rotation += 360;
const deltaRotation = rotation - this.gestureState.lastRotation;
this.emit('rotate', {
rotation: rotation,
deltaRotation: deltaRotation,
center: center,
touches: touches
});
this.gestureState.lastRotation = rotation;
}
}
// 清除长按定时器(移动时取消长按)
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
}
/**
* 处理触摸结束
* @param {TouchEvent} event - 触摸事件
*/
handleTouchEnd(event) {
event.preventDefault();
const changedTouches = Array.from(event.changedTouches);
const remainingTouches = Array.from(event.touches);
this.updateTouches(remainingTouches);
// 重置手势状态
if (remainingTouches.length === 0) {
this.gestureState.isActive = false;
}
// 单点手势检测
const duration = Date.now() - this.gestureState.startTime;
if (changedTouches.length === 1 && duration < this.options.tapTimeout) {
const touch = changedTouches[0];
const deltaX = Math.abs(touch.clientX - this.gestureState.startX);
const deltaY = Math.abs(touch.clientY - this.gestureState.startY);
// 点击检测
if (this.options.enableTap && deltaX < 10 && deltaY < 10) {
this.emit('tap', {
x: touch.clientX,
y: touch.clientY,
touch: touch
});
}
// 滑动检测
if (this.options.enableSwipe && (deltaX > this.options.swipeThreshold || deltaY > this.options.swipeThreshold)) {
let direction;
if (deltaX > deltaY) {
direction = touch.clientX > this.gestureState.startX ? 'right' : 'left';
} else {
direction = touch.clientY > this.gestureState.startY ? 'down' : 'up';
}
this.emit('swipe', {
direction: direction,
deltaX: touch.clientX - this.gestureState.startX,
deltaY: touch.clientY - this.gestureState.startY,
velocity: Math.sqrt(deltaX * deltaX + deltaY * deltaY) / duration,
touch: touch
});
}
}
// 清除长按定时器
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
}
/**
* 处理触摸取消
* @param {TouchEvent} event - 触摸事件
*/
handleTouchCancel(event) {
this.handleTouchEnd(event);
}
/**
* 更新触摸点
* @param {Array} touches - 触摸点数组
*/
updateTouches(touches) {
this.touches.clear();
touches.forEach(touch => {
this.touches.set(touch.identifier, {
x: touch.clientX,
y: touch.clientY,
timestamp: Date.now()
});
});
}
/**
* 获取两点间距离
* @param {Touch} touch1 - 触摸点1
* @param {Touch} touch2 - 触摸点2
* @returns {number}
*/
getDistance(touch1, touch2) {
const dx = touch2.clientX - touch1.clientX;
const dy = touch2.clientY - touch1.clientY;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 获取两点间角度
* @param {Touch} touch1 - 触摸点1
* @param {Touch} touch2 - 触摸点2
* @returns {number}
*/
getAngle(touch1, touch2) {
const dx = touch2.clientX - touch1.clientX;
const dy = touch2.clientY - touch1.clientY;
return Math.atan2(dy, dx) * 180 / Math.PI;
}
/**
* 获取两点中心
* @param {Touch} touch1 - 触摸点1
* @param {Touch} touch2 - 触摸点2
* @returns {Object}
*/
getCenter(touch1, touch2) {
return {
x: (touch1.clientX + touch2.clientX) / 2,
y: (touch1.clientY + touch2.clientY) / 2
};
}
/**
* 重置手势状态
*/
resetGestureState() {
this.gestureState = {
isActive: false,
startTime: 0,
startDistance: 0,
startAngle: 0,
lastScale: 1,
lastRotation: 0
};
}
/**
* 鼠标事件处理(桌面端测试)
*/
handleMouseDown(event) {
this.mouseDown = true;
this.handleTouchStart({
preventDefault: () => event.preventDefault(),
touches: [{
identifier: 0,
clientX: event.clientX,
clientY: event.clientY
}]
});
}
handleMouseMove(event) {
if (!this.mouseDown) return;
this.handleTouchMove({
preventDefault: () => event.preventDefault(),
touches: [{
identifier: 0,
clientX: event.clientX,
clientY: event.clientY
}]
});
}
handleMouseUp(event) {
if (!this.mouseDown) return;
this.mouseDown = false;
this.handleTouchEnd({
preventDefault: () => event.preventDefault(),
touches: [],
changedTouches: [{
identifier: 0,
clientX: event.clientX,
clientY: event.clientY
}]
});
}
/**
* 事件监听
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
/**
* 移除事件监听
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
*/
off(event, callback) {
const callbacks = this.listeners.get(event) || [];
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
/**
* 触发事件
* @param {string} event - 事件名
* @param {*} data - 事件数据
*/
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(callback => callback(data));
}
/**
* 销毁手势管理器
*/
destroy() {
// 移除事件监听器
this.element.removeEventListener('touchstart', this.handleTouchStart);
this.element.removeEventListener('touchmove', this.handleTouchMove);
this.element.removeEventListener('touchend', this.handleTouchEnd);
this.element.removeEventListener('touchcancel', this.handleTouchCancel);
this.element.removeEventListener('mousedown', this.handleMouseDown);
this.element.removeEventListener('mousemove', this.handleMouseMove);
this.element.removeEventListener('mouseup', this.handleMouseUp);
// 清理定时器
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
}
// 清理数据
this.touches.clear();
this.listeners.clear();
}
}
// 使用示例
const gestureElement = document.getElementById('gesture-area');
const gestureManager = new TouchGestureManager(gestureElement);
// 监听手势事件
gestureManager.on('tap', (data) => {
console.log('点击:', data);
});
gestureManager.on('swipe', (data) => {
console.log('滑动:', data.direction, data.velocity);
});
gestureManager.on('pinch', (data) => {
console.log('缩放:', data.scale);
gestureElement.style.transform = `scale(${data.scale})`;
});
gestureManager.on('rotate', (data) => {
console.log('旋转:', data.rotation);
gestureElement.style.transform = `rotate(${data.rotation}deg)`;
});
gestureManager.on('longpress', (data) => {
console.log('长按:', data);
});
15. 地理位置接口:位置服务的核心
🔍 应用场景
地图应用、位置签到、导航服务、基于位置的推荐
❌ 常见问题
javascript
// ❌ 简单的位置获取,缺乏错误处理和优化
navigator.geolocation.getCurrentPosition((position) => {
console.log(position.coords.latitude, position.coords.longitude);
});
✅ 推荐方案
javascript
// ✅ 专业的地理位置管理器
class GeolocationManager {
constructor(options = {}) {
this.options = {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 300000, // 5分钟缓存
...options
};
this.currentPosition = null;
this.watchId = null;
this.listeners = new Map();
this.isSupported = 'geolocation' in navigator;
}
/**
* 检查地理位置支持
* @returns {boolean}
*/
isGeolocationSupported() {
return this.isSupported;
}
/**
* 获取当前位置
* @param {Object} options - 选项
* @returns {Promise<Position>}
*/
async getCurrentPosition(options = {}) {
if (!this.isSupported) {
throw new Error('Geolocation is not supported');
}
const finalOptions = { ...this.options, ...options };
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(position) => {
this.currentPosition = position;
this.emit('positionUpdate', position);
resolve(position);
},
(error) => {
this.handleError(error);
reject(error);
},
finalOptions
);
});
}
/**
* 开始监听位置变化
* @param {Object} options - 选项
* @returns {number} watchId
*/
startWatching(options = {}) {
if (!this.isSupported) {
throw new Error('Geolocation is not supported');
}
if (this.watchId !== null) {
this.stopWatching();
}
const finalOptions = { ...this.options, ...options };
this.watchId = navigator.geolocation.watchPosition(
(position) => {
this.currentPosition = position;
this.emit('positionUpdate', position);
},
(error) => {
this.handleError(error);
},
finalOptions
);
return this.watchId;
}
/**
* 停止监听位置变化
*/
stopWatching() {
if (this.watchId !== null) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
this.emit('watchStopped');
}
}
/**
* 计算两点间距离
* @param {Object} pos1 - 位置1
* @param {Object} pos2 - 位置2
* @returns {number} 距离(米)
*/
calculateDistance(pos1, pos2) {
const R = 6371e3; // 地球半径(米)
const φ1 = pos1.latitude * Math.PI / 180;
const φ2 = pos2.latitude * Math.PI / 180;
const Δφ = (pos2.latitude - pos1.latitude) * Math.PI / 180;
const Δλ = (pos2.longitude - pos1.longitude) * 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;
}
/**
* 处理地理位置错误
* @param {GeolocationPositionError} error - 错误对象
*/
handleError(error) {
let message;
switch (error.code) {
case error.PERMISSION_DENIED:
message = '用户拒绝了地理位置请求';
break;
case error.POSITION_UNAVAILABLE:
message = '位置信息不可用';
break;
case error.TIMEOUT:
message = '获取位置信息超时';
break;
default:
message = '获取位置信息时发生未知错误';
break;
}
this.emit('error', { code: error.code, message });
}
/**
* 事件监听
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
/**
* 触发事件
* @param {string} event - 事件名
* @param {*} data - 事件数据
*/
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(callback => callback(data));
}
}
// 使用示例
const geoManager = new GeolocationManager({
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 600000
});
// 检查支持
if (geoManager.isGeolocationSupported()) {
// 获取当前位置
try {
const position = await geoManager.getCurrentPosition();
console.log('当前位置:', position.coords);
} catch (error) {
console.error('获取位置失败:', error);
}
// 监听位置变化
geoManager.on('positionUpdate', (position) => {
console.log('位置更新:', position.coords);
updateMap(position.coords);
});
geoManager.startWatching();
} else {
console.log('浏览器不支持地理位置');
}
16. 设备方向接口:感知设备状态
🔍 应用场景
移动端游戏、AR应用、设备姿态检测、重力感应
❌ 常见问题
javascript
// ❌ 简单的设备方向监听,缺乏数据处理
window.addEventListener('deviceorientation', (event) => {
console.log(event.alpha, event.beta, event.gamma);
});
✅ 推荐方案
javascript
// ✅ 专业的设备方向管理器
class DeviceOrientationManager {
constructor(options = {}) {
this.options = {
enableSmoothing: true,
smoothingFactor: 0.8,
threshold: 1, // 度数阈值
...options
};
this.isSupported = 'DeviceOrientationEvent' in window;
this.isListening = false;
this.listeners = new Map();
this.currentOrientation = {
alpha: 0, // Z轴旋转
beta: 0, // X轴旋转
gamma: 0, // Y轴旋转
absolute: false
};
this.smoothedOrientation = { ...this.currentOrientation };
this.lastOrientation = { ...this.currentOrientation };
}
/**
* 检查设备方向支持
* @returns {boolean}
*/
isDeviceOrientationSupported() {
return this.isSupported;
}
/**
* 请求权限(iOS 13+需要)
* @returns {Promise<boolean>}
*/
async requestPermission() {
if (typeof DeviceOrientationEvent.requestPermission === 'function') {
try {
const permission = await DeviceOrientationEvent.requestPermission();
return permission === 'granted';
} catch (error) {
console.error('设备方向权限请求失败:', error);
return false;
}
}
return true; // 其他平台默认允许
}
/**
* 开始监听设备方向
* @returns {Promise<boolean>}
*/
async startListening() {
if (!this.isSupported) {
throw new Error('Device orientation is not supported');
}
if (this.isListening) {
return true;
}
// 请求权限
const hasPermission = await this.requestPermission();
if (!hasPermission) {
throw new Error('Device orientation permission denied');
}
// 绑定事件处理器
this.handleDeviceOrientation = this.handleDeviceOrientation.bind(this);
window.addEventListener('deviceorientation', this.handleDeviceOrientation);
this.isListening = true;
this.emit('started');
return true;
}
/**
* 停止监听设备方向
*/
stopListening() {
if (!this.isListening) return;
window.removeEventListener('deviceorientation', this.handleDeviceOrientation);
this.isListening = false;
this.emit('stopped');
}
/**
* 处理设备方向事件
* @param {DeviceOrientationEvent} event - 设备方向事件
*/
handleDeviceOrientation(event) {
const newOrientation = {
alpha: event.alpha || 0,
beta: event.beta || 0,
gamma: event.gamma || 0,
absolute: event.absolute || false
};
// 数据平滑处理
if (this.options.enableSmoothing) {
this.smoothedOrientation = this.smoothOrientation(newOrientation);
} else {
this.smoothedOrientation = newOrientation;
}
// 检查变化阈值
if (this.hasSignificantChange(this.smoothedOrientation, this.lastOrientation)) {
this.currentOrientation = { ...this.smoothedOrientation };
this.lastOrientation = { ...this.smoothedOrientation };
// 触发事件
this.emit('orientationchange', {
orientation: this.currentOrientation,
raw: newOrientation
});
// 检测特定方向
this.detectOrientation();
}
}
/**
* 平滑方向数据
* @param {Object} newOrientation - 新的方向数据
* @returns {Object}
*/
smoothOrientation(newOrientation) {
const factor = this.options.smoothingFactor;
return {
alpha: this.smoothAngle(this.smoothedOrientation.alpha, newOrientation.alpha, factor),
beta: this.smoothAngle(this.smoothedOrientation.beta, newOrientation.beta, factor),
gamma: this.smoothAngle(this.smoothedOrientation.gamma, newOrientation.gamma, factor),
absolute: newOrientation.absolute
};
}
/**
* 平滑角度值
* @param {number} oldAngle - 旧角度
* @param {number} newAngle - 新角度
* @param {number} factor - 平滑因子
* @returns {number}
*/
smoothAngle(oldAngle, newAngle, factor) {
// 处理角度跨越(0-360度)
let diff = newAngle - oldAngle;
if (diff > 180) diff -= 360;
if (diff < -180) diff += 360;
return oldAngle + diff * (1 - factor);
}
/**
* 检查是否有显著变化
* @param {Object} current - 当前方向
* @param {Object} last - 上次方向
* @returns {boolean}
*/
hasSignificantChange(current, last) {
const threshold = this.options.threshold;
return Math.abs(current.alpha - last.alpha) > threshold ||
Math.abs(current.beta - last.beta) > threshold ||
Math.abs(current.gamma - last.gamma) > threshold;
}
/**
* 检测设备方向
*/
detectOrientation() {
const { beta, gamma } = this.currentOrientation;
let orientation = 'unknown';
// 检测设备方向
if (Math.abs(beta) < 45) {
if (Math.abs(gamma) < 45) {
orientation = 'flat';
} else if (gamma > 45) {
orientation = 'left';
} else if (gamma < -45) {
orientation = 'right';
}
} else if (beta > 45) {
orientation = 'forward';
} else if (beta < -45) {
orientation = 'backward';
}
this.emit('orientationdetected', {
orientation: orientation,
angles: this.currentOrientation
});
}
/**
* 获取当前方向
* @returns {Object}
*/
getCurrentOrientation() {
return { ...this.currentOrientation };
}
/**
* 计算倾斜角度
* @returns {Object}
*/
getTiltAngles() {
const { beta, gamma } = this.currentOrientation;
return {
pitch: beta, // 前后倾斜
roll: gamma, // 左右倾斜
magnitude: Math.sqrt(beta * beta + gamma * gamma)
};
}
/**
* 事件监听
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
/**
* 触发事件
* @param {string} event - 事件名
* @param {*} data - 事件数据
*/
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(callback => callback(data));
}
/**
* 销毁管理器
*/
destroy() {
this.stopListening();
this.listeners.clear();
}
}
// 使用示例
const orientationManager = new DeviceOrientationManager({
enableSmoothing: true,
smoothingFactor: 0.8,
threshold: 2
});
// 检查支持
if (orientationManager.isDeviceOrientationSupported()) {
// 开始监听
try {
await orientationManager.startListening();
// 监听方向变化
orientationManager.on('orientationchange', (data) => {
console.log('方向变化:', data.orientation);
updateUI(data.orientation);
});
// 监听方向检测
orientationManager.on('orientationdetected', (data) => {
console.log('设备方向:', data.orientation);
});
} catch (error) {
console.error('启动设备方向监听失败:', error);
}
} else {
console.log('设备不支持方向感应');
}
17. 网络信息接口:网络状态感知
🔍 应用场景
网络状态监控、自适应加载、离线处理、性能优化
❌ 常见问题
javascript
// ❌ 简单的网络状态检查,信息有限
if (navigator.onLine) {
console.log('在线');
} else {
console.log('离线');
}
✅ 推荐方案
javascript
// ✅ 专业的网络信息管理器
class NetworkManager {
constructor(options = {}) {
this.options = {
enableConnectionMonitoring: true,
enableSpeedTest: true,
speedTestInterval: 30000, // 30秒
speedTestUrl: '/api/ping',
...options
};
this.listeners = new Map();
this.connectionInfo = {
online: navigator.onLine,
type: 'unknown',
effectiveType: 'unknown',
downlink: 0,
rtt: 0,
saveData: false
};
this.speedTestResults = [];
this.speedTestTimer = null;
this.init();
}
/**
* 初始化网络管理器
*/
init() {
// 监听在线/离线状态
window.addEventListener('online', this.handleOnline.bind(this));
window.addEventListener('offline', this.handleOffline.bind(this));
// 监听网络连接变化
if ('connection' in navigator) {
const connection = navigator.connection;
connection.addEventListener('change', this.handleConnectionChange.bind(this));
this.updateConnectionInfo();
}
// 开始速度测试
if (this.options.enableSpeedTest) {
this.startSpeedTest();
}
}
/**
* 处理在线事件
*/
handleOnline() {
this.connectionInfo.online = true;
this.emit('online', this.connectionInfo);
this.emit('statuschange', this.connectionInfo);
// 重新开始速度测试
if (this.options.enableSpeedTest) {
this.startSpeedTest();
}
}
/**
* 处理离线事件
*/
handleOffline() {
this.connectionInfo.online = false;
this.emit('offline', this.connectionInfo);
this.emit('statuschange', this.connectionInfo);
// 停止速度测试
this.stopSpeedTest();
}
/**
* 处理连接变化
*/
handleConnectionChange() {
this.updateConnectionInfo();
this.emit('connectionchange', this.connectionInfo);
this.emit('statuschange', this.connectionInfo);
}
/**
* 更新连接信息
*/
updateConnectionInfo() {
if ('connection' in navigator) {
const connection = navigator.connection;
this.connectionInfo = {
...this.connectionInfo,
type: connection.type || 'unknown',
effectiveType: connection.effectiveType || 'unknown',
downlink: connection.downlink || 0,
rtt: connection.rtt || 0,
saveData: connection.saveData || false
};
}
}
/**
* 开始网络速度测试
*/
startSpeedTest() {
if (this.speedTestTimer) {
clearInterval(this.speedTestTimer);
}
// 立即执行一次
this.performSpeedTest();
// 定期执行
this.speedTestTimer = setInterval(() => {
this.performSpeedTest();
}, this.options.speedTestInterval);
}
/**
* 停止网络速度测试
*/
stopSpeedTest() {
if (this.speedTestTimer) {
clearInterval(this.speedTestTimer);
this.speedTestTimer = null;
}
}
/**
* 执行网络速度测试
*/
async performSpeedTest() {
if (!this.connectionInfo.online) return;
try {
const startTime = performance.now();
const response = await fetch(this.options.speedTestUrl + '?t=' + Date.now(), {
method: 'HEAD',
cache: 'no-cache'
});
const endTime = performance.now();
const latency = endTime - startTime;
const result = {
timestamp: Date.now(),
latency: latency,
success: response.ok
};
this.speedTestResults.push(result);
// 保留最近10次结果
if (this.speedTestResults.length > 10) {
this.speedTestResults.shift();
}
this.emit('speedtest', result);
} catch (error) {
const result = {
timestamp: Date.now(),
latency: -1,
success: false,
error: error.message
};
this.speedTestResults.push(result);
this.emit('speedtest', result);
}
}
/**
* 获取网络质量评估
* @returns {Object}
*/
getNetworkQuality() {
const { effectiveType, downlink, rtt } = this.connectionInfo;
let quality = 'unknown';
let score = 0;
// 基于有效连接类型评分
switch (effectiveType) {
case 'slow-2g':
score += 1;
break;
case '2g':
score += 2;
break;
case '3g':
score += 3;
break;
case '4g':
score += 4;
break;
}
// 基于下行速度评分
if (downlink > 10) score += 2;
else if (downlink > 5) score += 1;
// 基于RTT评分
if (rtt < 100) score += 2;
else if (rtt < 300) score += 1;
// 基于速度测试结果评分
const avgLatency = this.getAverageLatency();
if (avgLatency > 0) {
if (avgLatency < 100) score += 2;
else if (avgLatency < 300) score += 1;
}
// 确定质量等级
if (score >= 8) quality = 'excellent';
else if (score >= 6) quality = 'good';
else if (score >= 4) quality = 'fair';
else if (score >= 2) quality = 'poor';
else quality = 'very-poor';
return {
quality: quality,
score: score,
details: {
effectiveType: effectiveType,
downlink: downlink,
rtt: rtt,
avgLatency: avgLatency
}
};
}
/**
* 获取平均延迟
* @returns {number}
*/
getAverageLatency() {
const successfulTests = this.speedTestResults.filter(result => result.success);
if (successfulTests.length === 0) return -1;
const totalLatency = successfulTests.reduce((sum, result) => sum + result.latency, 0);
return totalLatency / successfulTests.length;
}
/**
* 获取连接信息
* @returns {Object}
*/
getConnectionInfo() {
return { ...this.connectionInfo };
}
/**
* 检查是否为慢速连接
* @returns {boolean}
*/
isSlowConnection() {
const { effectiveType, saveData } = this.connectionInfo;
return saveData || effectiveType === 'slow-2g' || effectiveType === '2g';
}
/**
* 检查是否为移动网络
* @returns {boolean}
*/
isMobileConnection() {
const { type } = this.connectionInfo;
return type === 'cellular';
}
/**
* 获取网络建议
* @returns {Object}
*/
getNetworkRecommendations() {
const quality = this.getNetworkQuality();
const recommendations = [];
if (this.isSlowConnection()) {
recommendations.push({
type: 'performance',
message: '检测到慢速网络,建议减少数据传输'
});
}
if (this.isMobileConnection()) {
recommendations.push({
type: 'data-usage',
message: '当前使用移动网络,注意流量消耗'
});
}
if (quality.quality === 'poor' || quality.quality === 'very-poor') {
recommendations.push({
type: 'quality',
message: '网络质量较差,建议启用离线模式'
});
}
return {
quality: quality,
recommendations: recommendations
};
}
/**
* 事件监听
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
/**
* 触发事件
* @param {string} event - 事件名
* @param {*} data - 事件数据
*/
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(callback => callback(data));
}
/**
* 销毁网络管理器
*/
destroy() {
window.removeEventListener('online', this.handleOnline);
window.removeEventListener('offline', this.handleOffline);
if ('connection' in navigator) {
navigator.connection.removeEventListener('change', this.handleConnectionChange);
}
this.stopSpeedTest();
this.listeners.clear();
}
}
// 使用示例
const networkManager = new NetworkManager({
enableSpeedTest: true,
speedTestInterval: 30000
});
// 监听网络状态变化
networkManager.on('statuschange', (info) => {
console.log('网络状态:', info);
updateNetworkIndicator(info);
});
networkManager.on('speedtest', (result) => {
console.log('速度测试:', result);
});
// 获取网络建议
const recommendations = networkManager.getNetworkRecommendations();
console.log('网络建议:', recommendations);
18. 辅助功能接口:无障碍访问支持
🔍 应用场景
无障碍访问、屏幕阅读器支持、键盘导航、语音控制
❌ 常见问题
javascript
// ❌ 缺乏无障碍支持的组件
function createButton(text) {
const button = document.createElement('button');
button.textContent = text;
return button;
}
✅ 推荐方案
javascript
// ✅ 专业的无障碍功能管理器
class AccessibilityManager {
constructor(options = {}) {
this.options = {
enableKeyboardNavigation: true,
enableScreenReader: true,
enableHighContrast: false,
enableFocusManagement: true,
announceChanges: true,
...options
};
this.focusHistory = [];
this.announcements = [];
this.keyboardTrapStack = [];
this.listeners = new Map();
this.init();
}
/**
* 初始化无障碍管理器
*/
init() {
// 创建屏幕阅读器公告区域
this.createAnnouncementRegion();
// 设置键盘导航
if (this.options.enableKeyboardNavigation) {
this.setupKeyboardNavigation();
}
// 设置焦点管理
if (this.options.enableFocusManagement) {
this.setupFocusManagement();
}
// 检测用户偏好
this.detectUserPreferences();
}
/**
* 创建屏幕阅读器公告区域
*/
createAnnouncementRegion() {
// 创建实时公告区域
this.liveRegion = document.createElement('div');
this.liveRegion.setAttribute('aria-live', 'polite');
this.liveRegion.setAttribute('aria-atomic', 'true');
this.liveRegion.className = 'sr-only';
this.liveRegion.style.cssText = `
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
`;
// 创建紧急公告区域
this.assertiveRegion = document.createElement('div');
this.assertiveRegion.setAttribute('aria-live', 'assertive');
this.assertiveRegion.setAttribute('aria-atomic', 'true');
this.assertiveRegion.className = 'sr-only';
this.assertiveRegion.style.cssText = this.liveRegion.style.cssText;
document.body.appendChild(this.liveRegion);
document.body.appendChild(this.assertiveRegion);
}
/**
* 设置键盘导航
*/
setupKeyboardNavigation() {
document.addEventListener('keydown', (event) => {
this.handleKeyboardNavigation(event);
});
// 添加跳转链接
this.addSkipLinks();
}
/**
* 添加跳转链接
*/
addSkipLinks() {
const skipLinks = document.createElement('div');
skipLinks.className = 'skip-links';
skipLinks.innerHTML = `
<a href="#main-content" class="skip-link">跳转到主要内容</a>
<a href="#navigation" class="skip-link">跳转到导航</a>
`;
// 样式
const style = document.createElement('style');
style.textContent = `
.skip-links {
position: absolute;
top: -40px;
left: 6px;
z-index: 1000;
}
.skip-link {
position: absolute;
top: -40px;
left: 6px;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
z-index: 1000;
}
.skip-link:focus {
top: 6px;
}
`;
document.head.appendChild(style);
document.body.insertBefore(skipLinks, document.body.firstChild);
}
/**
* 处理键盘导航
* @param {KeyboardEvent} event - 键盘事件
*/
handleKeyboardNavigation(event) {
const { key, ctrlKey, altKey, shiftKey } = event;
// Escape键处理
if (key === 'Escape') {
this.handleEscape(event);
}
// Tab键陷阱处理
if (key === 'Tab') {
this.handleTabTrap(event);
}
// 方向键导航
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) {
this.handleArrowNavigation(event);
}
// 快捷键处理
if (ctrlKey || altKey) {
this.handleShortcuts(event);
}
}
/**
* 处理Escape键
* @param {KeyboardEvent} event - 键盘事件
*/
handleEscape(event) {
// 关闭模态框
const modal = document.querySelector('[role="dialog"]:not([aria-hidden="true"])');
if (modal) {
this.closeModal(modal);
event.preventDefault();
return;
}
// 退出焦点陷阱
if (this.keyboardTrapStack.length > 0) {
this.exitKeyboardTrap();
event.preventDefault();
}
}
/**
* 处理Tab键陷阱
* @param {KeyboardEvent} event - 键盘事件
*/
handleTabTrap(event) {
if (this.keyboardTrapStack.length === 0) return;
const currentTrap = this.keyboardTrapStack[this.keyboardTrapStack.length - 1];
const focusableElements = this.getFocusableElements(currentTrap.container);
if (focusableElements.length === 0) return;
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (event.shiftKey) {
// Shift + Tab
if (document.activeElement === firstElement) {
lastElement.focus();
event.preventDefault();
}
} else {
// Tab
if (document.activeElement === lastElement) {
firstElement.focus();
event.preventDefault();
}
}
}
/**
* 处理方向键导航
* @param {KeyboardEvent} event - 键盘事件
*/
handleArrowNavigation(event) {
const activeElement = document.activeElement;
const role = activeElement.getAttribute('role');
// 处理菜单导航
if (role === 'menuitem' || activeElement.closest('[role="menu"]')) {
this.handleMenuNavigation(event);
}
// 处理表格导航
if (activeElement.tagName === 'TD' || activeElement.tagName === 'TH') {
this.handleTableNavigation(event);
}
// 处理网格导航
if (role === 'gridcell' || activeElement.closest('[role="grid"]')) {
this.handleGridNavigation(event);
}
}
/**
* 设置焦点管理
*/
setupFocusManagement() {
// 监听焦点变化
document.addEventListener('focusin', (event) => {
this.handleFocusIn(event);
});
document.addEventListener('focusout', (event) => {
this.handleFocusOut(event);
});
// 监听DOM变化
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
this.handleDOMChanges(mutation);
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
/**
* 处理焦点进入
* @param {FocusEvent} event - 焦点事件
*/
handleFocusIn(event) {
const element = event.target;
// 记录焦点历史
this.focusHistory.push({
element: element,
timestamp: Date.now()
});
// 限制历史记录长度
if (this.focusHistory.length > 10) {
this.focusHistory.shift();
}
// 触发焦点事件
this.emit('focuschange', {
element: element,
type: 'focusin'
});
}
/**
* 处理焦点离开
* @param {FocusEvent} event - 焦点事件
*/
handleFocusOut(event) {
this.emit('focuschange', {
element: event.target,
type: 'focusout'
});
}
/**
* 检测用户偏好
*/
detectUserPreferences() {
// 检测减少动画偏好
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
this.setReducedMotion(true);
}
// 检测高对比度偏好
if (window.matchMedia('(prefers-contrast: high)').matches) {
this.setHighContrast(true);
}
// 检测颜色方案偏好
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
this.setDarkMode(true);
}
}
/**
* 公告消息给屏幕阅读器
* @param {string} message - 消息内容
* @param {string} priority - 优先级 ('polite' | 'assertive')
*/
announce(message, priority = 'polite') {
const region = priority === 'assertive' ? this.assertiveRegion : this.liveRegion;
// 清空区域
region.textContent = '';
// 延迟添加消息,确保屏幕阅读器能检测到变化
setTimeout(() => {
region.textContent = message;
}, 100);
// 记录公告
this.announcements.push({
message: message,
priority: priority,
timestamp: Date.now()
});
if (this.options.announceChanges) {
this.emit('announcement', {
message: message,
priority: priority
});
}
}
/**
* 创建键盘陷阱
* @param {HTMLElement} container - 容器元素
* @param {Object} options - 选项
*/
createKeyboardTrap(container, options = {}) {
const trap = {
container: container,
previousFocus: document.activeElement,
options: {
returnFocus: true,
...options
}
};
this.keyboardTrapStack.push(trap);
// 设置初始焦点
const focusableElements = this.getFocusableElements(container);
if (focusableElements.length > 0) {
focusableElements[0].focus();
}
return trap;
}
/**
* 退出键盘陷阱
*/
exitKeyboardTrap() {
if (this.keyboardTrapStack.length === 0) return;
const trap = this.keyboardTrapStack.pop();
// 恢复焦点
if (trap.options.returnFocus && trap.previousFocus) {
trap.previousFocus.focus();
}
}
/**
* 获取可聚焦元素
* @param {HTMLElement} container - 容器元素
* @returns {HTMLElement[]}
*/
getFocusableElements(container) {
const selector = [
'a[href]',
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
'[contenteditable="true"]'
].join(', ');
return Array.from(container.querySelectorAll(selector))
.filter(element => {
return element.offsetWidth > 0 &&
element.offsetHeight > 0 &&
!element.hasAttribute('aria-hidden');
});
}
/**
* 设置减少动画
* @param {boolean} enabled - 是否启用
*/
setReducedMotion(enabled) {
document.documentElement.classList.toggle('reduce-motion', enabled);
this.emit('preferencechange', {
type: 'reduced-motion',
enabled: enabled
});
}
/**
* 设置高对比度
* @param {boolean} enabled - 是否启用
*/
setHighContrast(enabled) {
document.documentElement.classList.toggle('high-contrast', enabled);
this.emit('preferencechange', {
type: 'high-contrast',
enabled: enabled
});
}
/**
* 设置深色模式
* @param {boolean} enabled - 是否启用
*/
setDarkMode(enabled) {
document.documentElement.classList.toggle('dark-mode', enabled);
this.emit('preferencechange', {
type: 'dark-mode',
enabled: enabled
});
}
/**
* 创建无障碍按钮
* @param {Object} config - 按钮配置
* @returns {HTMLElement}
*/
createAccessibleButton(config) {
const button = document.createElement('button');
// 基本属性
button.textContent = config.text;
button.type = config.type || 'button';
// 无障碍属性
if (config.ariaLabel) {
button.setAttribute('aria-label', config.ariaLabel);
}
if (config.ariaDescribedBy) {
button.setAttribute('aria-describedby', config.ariaDescribedBy);
}
if (config.ariaExpanded !== undefined) {
button.setAttribute('aria-expanded', config.ariaExpanded);
}
if (config.ariaControls) {
button.setAttribute('aria-controls', config.ariaControls);
}
// 事件处理
if (config.onClick) {
button.addEventListener('click', config.onClick);
}
// 键盘事件
button.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
button.click();
}
});
return button;
}
/**
* 事件监听
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
/**
* 触发事件
* @param {string} event - 事件名
* @param {*} data - 事件数据
*/
emit(event, data) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(callback => callback(data));
}
/**
* 销毁无障碍管理器
*/
destroy() {
// 清理DOM元素
if (this.liveRegion) {
this.liveRegion.remove();
}
if (this.assertiveRegion) {
this.assertiveRegion.remove();
}
// 清理事件监听器
this.listeners.clear();
// 清理键盘陷阱
while (this.keyboardTrapStack.length > 0) {
this.exitKeyboardTrap();
}
}
}
// 使用示例
const accessibilityManager = new AccessibilityManager({
enableKeyboardNavigation: true,
enableScreenReader: true,
announceChanges: true
});
// 创建无障碍按钮
const button = accessibilityManager.createAccessibleButton({
text: '打开菜单',
ariaLabel: '打开主导航菜单',
ariaExpanded: false,
ariaControls: 'main-menu',
onClick: () => {
// 切换菜单状态
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !isExpanded);
// 公告状态变化
accessibilityManager.announce(
isExpanded ? '菜单已关闭' : '菜单已打开'
);
}
});
// 监听无障碍事件
accessibilityManager.on('announcement', (data) => {
console.log('屏幕阅读器公告:', data.message);
});
accessibilityManager.on('focuschange', (data) => {
console.log('焦点变化:', data.element.tagName);
});
🎯 总结与展望
核心要点回顾
通过本篇文章,我们深入探讨了HTML DOM API的高级接口:
- Web Worker接口 - 实现多线程处理,提升应用性能
- WebRTC接口 - 构建实时通信应用
- WebGL接口 - 创建高性能3D图形
- 触摸手势接口 - 优化移动端交互体验
- 地理位置接口 - 实现位置感知功能
- 设备方向接口 - 感知设备状态变化
- 网络信息接口 - 智能网络状态管理
- 辅助功能接口 - 构建无障碍访问体验
实践建议
- 渐进增强 - 始终提供基础功能的降级方案
- 性能优化 - 合理使用高级API,避免过度消耗资源
- 用户体验 - 关注不同设备和网络环境下的体验
- 无障碍访问 - 确保所有用户都能正常使用应用
相关资源
感谢阅读! 如果这篇文章对你有帮助,请点赞、收藏并分享给更多的开发者。让我们一起构建更好的Web应用!
💡 提示:本文涵盖的API接口较多,建议结合实际项目需求选择性学习和应用。记住,技术的价值在于解决实际问题,而不是为了使用而使用。