前端与硬件设备交互探秘,最全总结!

开发 前端
在现代Web开发中,处理用户输入事件是构建交互式应用的关键部分。这些输入事件可以来自多种设备,包括键盘、鼠标(或触控板)、触摸屏以及游戏控制器等。

随着技术的发展,前端技术与硬件设备的交互模式正变得日益丰富多样。从基础的点击、滑动操作,进化到高级的传感器数据捕获及设备远程操控,前端技术正持续跨越传统的交互壁垒,为用户塑造出更为便捷且智能的使用体验。本文就来对前端与硬件交互多种方式进行全面盘点。

接收设备的输入

在现代Web开发中,处理用户输入事件是构建交互式应用的关键部分。这些输入事件可以来自多种设备,包括键盘、鼠标(或触控板)、触摸屏以及游戏控制器等。

键盘事件

键盘事件是最常见的用户输入方式之一,包括按键按下(keydown)、按键释放(keyup)等事件。

  • keydown:当按键被按下时触发。
  • keyup:当按键被释放时触发。

监听键盘事件的基本方法是使用addEventListener方法:

document.addEventListener('keydown', function(event) {  
    // 使用event.key来获取按键的值  
    console.log('Key down:', event.key);  
});

Pointer事件

Pointer 事件是一个统一的指针输入模型,旨在处理所有类型的指针输入,包括鼠标、触控笔和触摸。Pointer事件包括pointerdown、pointerup、pointermove等。

  • pointerdown:当指针按下时触发。
  • pointerup:当指针释放时触发。
  • pointermove:当指针移动时触发。

监听Pointer事件的例子:

document.addEventListener('pointerdown', function(event) {
    console.log('Pointer down at:', event.clientX, event.clientY);  
});

还可以直接使用鼠标事件,常见的鼠标事件包括:

  • click:单击鼠标左键时触发。
  • dblclick:双击鼠标左键时触发。
  • mousedown:鼠标按钮被按下时触发。
  • mouseup:鼠标按钮被松开时触发。
  • mousemove:鼠标在元素上移动时触发。
  • mouseenter:鼠标进入元素时触发。
  • mouseleave:鼠标离开元素时触发。
  • contextmenu:用户右键点击时触发。
  • wheel:鼠标滚轮滚动时触发。
  • auxclick:辅助键(如中键或右键)被点击时触发。

例子:

document.addEventListener('mousedown', function(event) {  
    // 使用event.button来判断哪个鼠标按钮被按下  
    let buttonPressed;  
    switch (event.button) {  
        case 0:  
            buttonPressed = 'Left button';  
            break;  
        case 1:  
            buttonPressed = 'Middle button';  
            break;  
        case 2:  
            buttonPressed = 'Right button';  
            break;  
        default:  
            buttonPressed = 'Unknown button';  
    }  

    console.log(buttonPressed);

    // 如果按下了右键,则阻止默认的上下文菜单  
    if (event.button === 2) {  
        event.preventDefault();
    }  
});

mousedown事件对象的button属性表示被按下的鼠标按钮。这个属性的值如下:

  • 0:表示主按钮(通常是左键)。
  • 1:表示中间按钮(通常是滚轮按钮,但不是所有鼠标都有)。
  • 2:表示次按钮(通常是右键)。

游戏控制器

Gamepad API 允许 Web 应用检测并响应来自游戏控制器的输入,包括按钮按压和轴移动。这对于开发需要精确控制的游戏或应用非常有用。

使用 Gamepad API,首先需要检查是否有连接的游戏控制器:

function checkGamepads() {  
    const gamepads = navigator.getGamepads();  
    for (let i = 0; i < gamepads.length; i++) {  
        if (gamepads[i]) {  
            console.log('Gamepad connected:', gamepads[i]);  
            // 处理游戏控制器输入  
        }  
    }  
}  
  
// 定期检查是否有游戏控制器连接  
setInterval(checkGamepads, 100);

一旦检测到游戏控制器,可以访问其buttons和axes属性来读取按钮状态和轴位置:

if (gamepad.buttons[0].pressed) {  
    console.log('Button 0 pressed');  
}  
  
console.log('Axis 0:', gamepad.axes[0]); // 通常表示左右移动  
console.log('Axis 1:', gamepad.axes[1]); // 通常表示上下移动

获取音频和视频

在现代Web开发中,访问和处理音频与视频流是一项强大的功能,它允许开发者创建实时通信应用、视频录制工具、音频处理应用等。MediaDevices.getUserMedia() API 是实现这一功能的关键接口,它允许Web应用请求访问用户的音频和/或视频设备(如摄像头和麦克风),并获取实时媒体流。

获取实时音频和视频流

MediaDevices.getUserMedia() 是一个异步函数,它提示用户允许Web应用访问其音频和/或视频设备。一旦用户授权,该函数返回一个 Promise,该 Promise 解析为一个 MediaStream 对象,该对象包含了来自音频和/或视频设备的实时数据。

navigator.mediaDevices.getUserMedia({ audio: true, video: true })  
  .then(function(stream) {  
    // 使用视频流,例如将其显示在video元素上  
    var video = document.querySelector('video');  
    video.srcObject = stream;  
    video.play();  
  })  
  .catch(function(err) {  
    console.error("Error accessing media devices.", err);  
  });

注意,要使用摄像头或麦克风,需要申请权限。navigator.mediaDevices.getUserMedia() 的第一个参数是一个对象,用于指定详细信息和每种媒体类型的要求例如,如果要访问摄像头,则第一个参数应为 {video: true}。如需同时使用麦克风和摄像头,就要传递 {video: true, audio: true}。

浏览器在调用 navigator.mediaDevices.getUserMedia() 时显示权限对话框, 让用户能够选择授予或拒绝对其摄像头/麦克风的访问权限。

控制摄像头

在访问用户摄像头前,需要获取用户的授权。这里还是调用navigator.mediaDevices.getUserMedia()方法来实现,它会返回一个Promise对象。当用户同意或拒绝访问摄像头时,Promise对象会相应地resolve或reject。

const constraints = { video: true, audio: true };  
try {  
  const stream = await navigator.mediaDevices.getUserMedia(constraints);  
  // 成功获取到视频流,可以在这里进行后续处理  
} catch (err) {  
  // 用户拒绝或其他错误,可以在这里处理错误  
  console.error("Error accessing media devices.", err);  
}

成功获取到视频流后,可以将其赋值给HTML中的元素的srcObject属性,并调用play()方法播放视频流。

<video id="video" width="640" height="480" autoplay></video>  
<script>  
  const video = document.querySelector('#video');  
  navigator.mediaDevices.getUserMedia({ video: true, audio: false })  
    .then(function(stream) {  
      video.srcObject = stream;  
      video.play();  
    })  
    .catch(function(err) {  
      console.error("Error accessing media devices.", err);  
    });  
</script>

可以通过MediaStream API提供的getVideoTracks()方法可以获取到视频轨道,并通过enabled属性来打开或关闭摄像头。

const tracks = stream.getVideoTracks();  
tracks[0].enabled = false; // 关闭第一条视频轨道(即摄像头)

可以使用MediaRecorder API来录制视频并保存为文件。

const mediaRecorder = new MediaRecorder(stream);  
let chunks = [];  
  
mediaRecorder.ondataavailable = function(event) {  
  chunks.push(event.data);  
};  
  
mediaRecorder.onstop = function() {  
  const blob = new Blob(chunks, { type: 'video/mp4' });  
  const videoURL = window.URL.createObjectURL(blob);  
  // 这里可以将videoURL赋值给某个<video>元素的src属性来播放录制的视频  
  // 或者将blob对象上传到服务器  
  chunks = []; // 清空chunks数组,以便下次录制  
};  
  
// 开始录制  
mediaRecorder.start();  
  
// 停止录制  
// mediaRecorder.stop(); // 可以在需要的时候调用

可以使用Canvas API对当前摄像头画面进行截图。

const canvas = document.createElement('canvas');  
canvas.width = video.videoWidth;  
canvas.height = video.videoHeight;  
const ctx = canvas.getContext('2d');  
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);  
// 将canvas内容转换为图片URL并显示或下载  
const imageURL = canvas.toDataURL('image/png');  
// 可以在这里将imageURL赋值给某个<img>元素的src属性来显示图片,或者使用a标签的download属性来下载图片

使用设备进行打印

当调用 window.print() 方法时,浏览器会显示一个打印对话框,允许用户配置打印设置(如页面布局、纸张大小、打印份数等)。

使用设备进行身份验证

Web Authentication API 是一个现代、开放标准的 API,旨在通过公钥加密技术来简化用户认证过程,同时增强安全性。它支持多种身份验证方式,包括使用蓝牙、NFC(近场通信)、USB漫游设备(如U2F或FIDO2身份验证器)以及平台身份验证器(如内置在笔记本电脑或智能手机中的指纹识别器或屏幕锁定机制)。Authentication 它利用公钥密码学,在用户的设备上生成密钥对(公钥和私钥),其中公钥存储在服务器上,私钥则安全地保存在用户设备上。

注意:在 Web Authentication API 中,“挑战”(challenge)指的是一个由服务器生成的唯一、随机的数据字符串。它有助于防止重放攻击、增强安全性,并确保请求的真实性和完整性。

注册过程

  • 生成挑战:服务器生成一个唯一的挑战(通常是一个随机字节数组),并将其发送给客户端。
  • 调用API:客户端使用navigator.credentials.create()方法开始注册过程。该方法接受一个包含多个选项的对象作为参数,包括应用ID(通常是域名的哈希值)、用户ID(如用户名或邮箱地址)、公钥参数(指定使用的公钥算法和类型)以及挑战等。
  • 用户交互:用户被提示选择或插入一个身份验证器,并按照设备要求进行身份验证(如输入PIN码、指纹识别等)。
  • 生成并返回凭据:身份验证器生成一个公钥凭据,并将其与用户的私钥一起存储。公钥和必要的元数据(如认证器类型、公钥算法等)被封装在一个响应对象中,并返回给客户端。
  • 存储凭据:客户端将响应对象发送回服务器,服务器提取公钥并存储在数据库中,与用户ID相关联。

认证过程

  • 生成挑战:与注册过程类似,服务器生成一个新的挑战。
  • 调用API:客户端使用navigator.credentials.get()方法开始认证过程。该方法接受一个包含挑战、应用ID以及允许重用的凭据列表(可选)的对象作为参数。
  • 用户交互:用户再次被提示选择或插入身份验证器,并按照设备要求进行身份验证。
  • 生成并返回断言:身份验证器使用私钥签署挑战,并将签名连同凭据ID封装在一个断言对象中,返回给客户端。
  • 验证断言:客户端将断言对象发送回服务器,服务器使用存储的公钥验证签名,以确认用户身份。

访问设备上的文件

在Web开发中,处理本地文件的需求很常见。随着Web技术的发展,现代浏览器提供了几种API来处理用户文件,其中包括File System Access API和较老的File API。

File System Access API

File System Access API 提供了 showOpenFilePicker 和 showSaveFilePicker 方法,分别用于打开文件选择对话框和保存文件对话框。

async function readFileAndSave() {
  try {
    // 打开文件选择对话框
    const [fileHandle] = await window.showOpenFilePicker();
    
    // 读取文件内容
    const file = await fileHandle.getFile();
    const contents = await file.text();
    
    console.log('文件内容:', contents);
    
    // 打开保存文件对话框
    const saveOptions = {
      suggestedName: 'saved-file.txt',
      types: [
        {
          description: 'Text files',
          accept: {
            'text/plain': ['.txt'],
          },
        },
      ],
    };
    
    const saveHandle = await window.showSaveFilePicker(saveOptions);
    
    // 写入文件内容
    const writableStream = await saveHandle.createWritable();
    await writableStream.write(contents);
    await writableureStream.close();
    
    console.log('文件已保存');
  } catch (error) {
    console.error('发生错误:', error);
  }
}

File API

如果 File System Access API 不可用,可以使用 File API 来实现类似的功能。File API 提供了 FileReader 对象,用于读取文件内容。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>读取文件</title>

</head>

<body>
  <input type="file" id="fileInput" multiple>
  <pre id="fileContent"></pre>

  <script>
    document.getElementById('fileInput').addEventListener('change', function(event) {
      const files = event.target.files;
      const fileContentElement = document.getElementById('fileContent');
      
      fileContentElement.textContent = '';
      
      for (const file of files) {
        const reader = new FileReader();
        
        reader.onload = function(e) {
          fileContentElement.textContent += e.target.result + '\n';
        };
        
        reader.readAsText(file);
      }
    });
  </script>

</body>

</html>

在这个例子中,创建了一个文件输入框,用户可以通过它选择本地文件。当用户选择文件后,使用 FileReader 对象来读取文件内容,并将其显示在页面上。

访问设备上的传感器

通用传感器 API(Generic Sensor API)是一种允许 Web 应用访问设备上的传感器数据的标准。这些传感器包括移动传感器(如加速度计和陀螺仪)以及环境传感器(如环境光传感器和磁力计)。这些API使得网页和应用能够利用设备的硬件传感器来提供更丰富的用户体验。

通用传感器 API 的主要组件如下:

  • Sensor API:提供了一个通用的接口来访问各种类型的传感器数据。
  • Motion Sensors:包括加速度计(Accelerometer)和陀螺仪(Gyroscope)。
  • Orientation Sensors:通常与设备的方向有关,可以提供关于设备方向的详细信息。
  • Environmental Sensors:包括环境光传感器和磁力计。

使用加速度计:

if ('Accelerometer' in window) {
  const accelerometer = new Accelerometer();

  accelerometer.addEventListener('reading', () => {
    console.log('Acceleration X:', accelerometer.x);
    console.log('Acceleration Y:', accelerometer.y);
    console.log('Acceleration Z:', accelerometer.z);
  });

  accelerometer.start();

  // 停止传感器
  // accelerometer.stop();
} else {
  console.log('Accelerometer is not supported on this device.');
}

使用环境光传感器:

if ('AmbientLightSensor' in window) {
  const ambientLightSensor = new AmbientLightSensor();

  ambientLightSensor.addEventListener('reading', () => {
    console.log('Ambient Light Level:', ambientLightSensor.illuminance);
  });

  ambientLightSensor.start();
} else {
  console.log('AmbientLightSensor is not supported on this device.');
}

DeviceMotion和DeviceOrientation

如果通用传感器API不可用,可以使用DeviceMotion和DeviceOrientation事件来访问设备的加速度计、陀螺仪和罗盘数据。

使用DeviceMotion事件获取加速度计数据:

window.addEventListener('devicemotion', (event) => {
  console.log('Acceleration X:', event.acceleration.x);
  console.log('Acceleration Y:', event.acceleration.y);
  console.log('Acceleration Z:', event.acceleration.z);

  console.log('Acceleration Including Gravity X:', event.accelerationIncludingGravity.x);
  console.log('Acceleration Including Gravity Y:', event.accelerationIncludingGravity.y);
  console.log('Acceleration Including Gravity Z:', event.accelerationIncludingGravity.z);
});

使用DeviceOrientation事件获取陀螺仪和罗盘数据:

window.addEventListener('deviceorientation', (event) => {
  console.log('Alpha:', event.alpha); // 绕Z轴旋转(罗盘方向)
  console.log('Beta:', event.beta);   // 绕X轴旋转(前后倾斜)
  console.log('Gamma:', event.gamma); // 绕Y轴旋转(左右倾斜)
});

访问 GPS 坐标

在 web 上可以使用 Geolocation API 获取设备的当前地理位置(通常是经纬度坐标)。

首先,网页需要请求用户的权限来获取其地理位置。这是因为地理位置信息属于用户的隐私数据,必须得到用户的明确同意。一旦用户授予权限,可以使用navigator.geolocation.getCurrentPosition()方法来获取当前位置。这个方法接受两个回调函数作为参数:成功回调和失败回调。

  • 成功回调:当成功获取到位置信息时调用,接收一个位置对象作为参数。
  • 失败回调:当获取位置信息失败时调用,接收一个错误对象作为参数。
// 请求用户位置  
if (navigator.geolocation) {  
  navigator.geolocation.getCurrentPosition(successCallback, errorCallback);  
} else {  
  console.log("Geolocation is not supported by this browser.");  
}  
  
// 成功回调  
function successCallback(position) {  
  const latitude = position.coords.latitude;  
  const longitude = position.coords.longitude;  
  console.log(`Latitude: ${latitude}, Longitude: ${longitude}`);  
}  
  
// 失败回调  
function errorCallback(error) {  
  switch(error.code) {  
    case error.PERMISSION_DENIED:  
      console.log("User denied the request for Geolocation.");  
      break;  
    case error.POSITION_UNAVAILABLE:  
      console.log("Location information is unavailable.");  
      break;  
    case error.TIMEOUT:  
      console.log("The request to get user location timed out.");  
      break;  
    case error.UNKNOWN_ERROR:  
      console.log("An unknown error occurred.");  
      break;  
  }  
}

检查设备电池电量

使用Battery API确实可以获取关于电池电量的信息,并且在电池电量或充电状态发生变化时收到通知。

获取电量信息

  1. 检测浏览器支持
  • 在使用Battery API之前,开发者需要先检测用户所使用的浏览器是否支持该API。
  • 可以通过if ('getBattery' in navigator)来判断浏览器是否支持Battery API。
  1. 获取电池对象
  • 如果浏览器支持Battery API,可以通过调用navigator.getBattery()方法来获取一个Promise对象。
  • 这个Promise对象在成功时会传递一个BatteryManager对象,该对象包含了电池的各种信息。
  1. 读取电池信息
  • battery.level:表示当前电池的电量,范围从0到1,可以通过乘以100来转换为百分比形式。
  • battery.charging:一个布尔值,表示设备当前是否在充电。
  • battery.chargingTime:表示设备预计还需要多长时间充满,单位为秒(如果设备已经在充电且即将充满,则可能为0或接近0)。
  • battery.dischargingTime:表示设备预计还能使用多长时间,单位为秒(如果设备正在放电且电量即将耗尽,则可能为一个较大的值或Infinity)。
  • 一旦获取到BatteryManager对象,开发者就可以通过该对象的属性和事件来获取电池信息。
  • 主要的属性包括:

电池、电量状态通知

  1. 监听电池状态变化
  • 为了更加精准地捕捉和响应用户设备的电池状态动态,开发者需要实时监听电池状态的变化。
  • 可以通过为BatteryManager对象注册事件监听器来实现这一功能。
  1. 事件类型
  • levelchange:当设备电量发生变化时触发。
  • chargingchange:当设备的充电状态发生变化时触发(例如,从充电状态变为未充电状态,或反之)。
  • chargingtimechange:当设备充满电所需时间发生变化时触发(这通常发生在充电速率改变时)。
  • dischargingtimechange:当设备放空电所需时间发生变化时触发(这通常发生在电池使用量或放电速率改变时)。
  1. 实现方式
  • 使用battery.addEventListener()方法来为上述事件类型注册监听器。
  • 在监听器的回调函数中,可以更新UI或执行其他逻辑来响应电池状态的变化。

以下是一个简单的例子:

if ('getBattery' in navigator) {  
  navigator.getBattery().then(function(battery) {  
    // 初始获取电池信息  
    console.log('当前电池电量: ' + battery.level * 100 + '%');  
    console.log('充电状态: ' + (battery.charging ? '正在充电' : '未充电'));  
    console.log('充满电所需时间: ' + battery.chargingTime + '秒');  
    console.log('防空电所需时间: ' + battery.dischargingTime + '秒');  
  
    // 监听电池状态变化  
    battery.addEventListener('levelchange', function() {  
      console.log('电量已更新: 当前电量为' + (battery.level * 100) + '%');  
    });  
  
    battery.addEventListener('chargingchange', function() {  
      console.log('充电状态已变更: ' + (battery.charging ? '正在充电' : '未在充电'));  
    });  
  
    battery.addEventListener('chargingtimechange', function() {  
      console.log('充满电所需时间更新: 剩余' + battery.chargingTime + '秒');  
    });  
  
    battery.addEventListener('dischargingtimechange', function() {  
      console.log('放空电所需时间更新: 剩余' + battery.dischargingTime + '秒');  
    });  
  }).catch(function(error) {  
    // 处理获取电池对象失败的情况  
    console.error('获取电池信息失败:', error);  
  });  
} else {  
  // 处理浏览器不支持Battery API的情况  
  console.log('当前浏览器不支持Battery API');  
}

本地网络中的多媒体播放控制

Remote Playback API

Remote Playback API 允许网页应用将音频和视频内容发送到远程播放设备(如智能电视、无线音箱等)进行播放。这对于构建家庭娱乐系统或跨设备媒体共享功能特别有用。

工作原理

  1. 检测支持:首先,应用需要检测浏览器是否支持 Remote Playback API。
  2. 获取可用设备:通过调用 navigator.mediaDevices.enumerateDevices() 并过滤出支持 Remote Playback 的设备,可以获取到当前网络中的可用设备列表。但请注意,Remote Playback API 并不直接提供设备枚举功能;通常,设备发现是通过其他机制(如 UPnP、DLNA 等)或用户选择来实现的。
  3. 建立连接:一旦选择了目标设备,应用就可以通过 navigator.remotePlayback.requestRemotePlayback() 方法尝试与该设备建立连接。
  4. 发送媒体:连接建立后,应用可以开始将媒体内容发送到远程设备进行播放。

假设有一个网页应用,它允许用户选择并播放存储在本地服务器上的视频文件。该应用可以使用 Remote Playback API 将所选视频发送到用户的智能电视进行播放。

// 伪代码示例,具体实现可能因设备和浏览器而异  
if ('remotePlayback' in navigator.mediaDevices) {  
  // 假设已经通过某种方式选择了目标设备  
  const remoteDevice = selectedDevice;  
    
  navigator.mediaDevices.requestRemotePlayback(remoteDevice)  
    .then(remote => {  
      // 成功建立连接,开始播放媒体  
      const mediaElement = document.querySelector('video'); // 假设页面上有一个 <video> 元素  
      remote.loadVideo(URL.createObjectURL(mediaElement.currentSrc));  
      remote.play();  
    })  
    .catch(error => {  
      // 处理连接失败的情况  
      console.error('Remote playback failed:', error);  
    });  
} else {  
  console.log('Remote Playback API is not supported in this browser.');  
}

Presentation API

Presentation API 则用于在第二屏幕(如辅助屏幕、HDMI 连接的显示屏、无线连接的智能电视等)上呈现网页内容。它允许开发者将网页的一部分或全部内容投影到另一个屏幕上,这对于会议演示、家庭娱乐等场景非常有用。

工作原理

  1. 检测支持:首先,应用需要检测浏览器是否支持 Presentation API。
  2. 获取可用屏幕:通过调用 navigator.presentation.getAvailability() 和 navigator.presentation.requestSession() 方法,应用可以获取到当前可用的第二屏幕列表,并请求与其中一个屏幕建立会话。
  3. 建立连接并发送内容:一旦会话建立,应用就可以开始将内容发送到第二屏幕进行显示。

假设有一个网页应用,它允许用户将当前页面或某个特定部分的内容投影到连接在同一本地网络上的智能电视上进行显示。

if ('presentation' in navigator) {  
  navigator.presentation.getAvailability().then(availability => {  
    if (availability === 'available') {  
      // 请求与第二屏幕建立会话  
      navigator.presentation.requestSession().then(presentationSession => {  
        // 会话建立成功,开始发送内容  
        // 假设我们有一个要投影的 <div> 元素  
        const contentToPresent = document.querySelector('#content-to-present');  
          
        // 创建一个新的 HTML 文档,并将要投影的内容添加到其中  
        const presentationDocument = presentationSession.urls[0]; // 获取用于投影的 URL  
        const iframe = document.createElement('iframe');  
        iframe.src = presentationDocument;  
        iframe.onload = () => {  
          const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;  
          iframeDoc.open();  
          iframeDoc.write(`  
            <!DOCTYPE html>  
            <html>  
            <head><title>Presentation</title></head>  
            <body>${contentToPresent.innerHTML}</body>  
            </html>  
          `);  
          iframeDoc.close();  
        };  
          
        // 将 iframe 添加到当前页面中(虽然实际上它不会在当前页面显示)  
        // 这是为了触发 onload 事件并加载投影内容  
        document.body.appendChild(iframe);  
          
        // 稍后可以从 DOM 中移除 iframe,因为它不再需要  
        // document.body.removeChild(iframe);  
          
      }).catch(error => {  
        // 处理请求会话失败的情况  
        console.error('Presentation session request failed:', error);  
      });  
    } else {  
      console.log('No second screens available.');  
    }  
  }).catch(error => {  
    // 处理获取可用性失败的情况  
    console.error('Presentation API availability check failed:', error);  
  });  
} else {  
  console.log('Presentation API is not supported in this browser.');  
}
责任编辑:姜华 来源: 前端充电宝
相关推荐

2024-03-22 15:32:21

机器学习算法

2010-08-10 17:01:48

FlexJavaScript

2023-12-05 07:45:35

SQL数据库

2024-04-03 09:27:49

后端开发数据库内核

2019-04-04 09:08:41

2009-12-16 11:11:44

HTML 5

2011-09-02 14:59:15

2024-03-01 20:55:40

Pytorch张量Tensor

2009-12-07 09:39:04

Linux设备驱动硬件通信

2009-10-21 17:02:51

综合布线施工经验

2010-03-15 15:45:50

2013-01-09 10:34:13

OpenStackKVM

2023-01-03 17:31:52

2021-04-27 08:31:10

前端应用场景

2009-12-16 15:59:13

Linux设备文件管理

2021-12-29 09:00:36

Go文件syscall

2015-05-27 14:55:45

2024-07-26 10:35:00

2023-03-11 22:22:04

数据库MySQL
点赞
收藏

51CTO技术栈公众号