自定义元素无法直接访问硬件接口,必须通过浏览器标准api(如getusermedia、requestdevice)在安全上下文和用户手势下调用,并封装为组件内状态与事件。

自定义元素不能直接访问硬件接口
原生 Web Components(Custom Elements)本身不具备调用摄像头、麦克风、GPS、蓝牙或 USB 设备的能力——它只是 DOM 元素的封装机制,不提供底层硬件访问权限。所有硬件操作必须通过浏览器提供的标准 API(如 navigator.mediaDevices.getUserMedia、navigator.geolocation、navigator.bluetooth 等)触发,且这些 API 要求用户主动授权、上下文必须是安全环境(https:// 或 localhost)。
封装硬件调用时必须代理到内部原生元素或显式请求权限
如果你希望用 <camera-preview></camera-preview> 这类自定义标签“封装”摄像头功能,实际逻辑只能是:在 connectedCallback 中调用 navigator.mediaDevices.getUserMedia,将返回的 MediaStream 绑定到内部一个真实的 <video></video> 元素(放在 Shadow DOM 内),再透传错误、加载状态和流事件。
- 不能在
constructor里调用getUserMedia—— 此时 DOM 未挂载,且该 API 不允许在非用户手势上下文中调用 - 必须监听
permission denied错误并暴露为自定义事件(如this.dispatchEvent(new Event('error'))),不能静默失败 - 若需支持
deviceid属性切换摄像头,得先调用enumerateDevices()获取设备列表,再传入getUserMedia({ video: { deviceId: { exact: value } } }) - 不要尝试在 Shadow DOM 外部用
document.querySelector('video')拿流——<video></video>必须在组件内部创建并绑定srcObject
Web Bluetooth 和 Web Serial 需显式用户手势触发
navigator.bluetooth.requestDevice() 和 navigator.serial.requestPort() 这类 API 强制要求调用必须来自用户可感知的手势(如 click、touchend),无法在 connectedCallback 自动执行。这意味着你不能靠“一插入页面就连设备”,而必须提供一个明确的按钮(例如 <button slot="connect">连接设备</button>),并在其 click 回调中发起请求。
- 自定义元素可以封装 UI 和状态管理(如
connecting、connected属性),但不能绕过浏览器的安全模型 - 连接成功后,应把
BluetoothDevice或SerialPort实例挂载到组件实例上(如this._device = device),供外部 JS 调用方法(如write()、read()) - 务必监听
gattserverdisconnected或disconnect事件,并同步更新组件状态,否则 UI 会与真实连接状态脱节
安全上下文与跨域限制是硬性门槛
即使代码逻辑完全正确,navigator.geolocation.getCurrentPosition、getUserMedia、requestDevice 等全部会直接拒绝执行,如果当前页面不是运行在 https:// 或 http://localhost 下。开发阶段可用 localhost,但部署到子域名(如 app.example.com)时,必须确保 HTTPS 已启用,且证书有效。
- 检查是否处于安全上下文:
if (!window.isSecureContext) { throw new Error('Not in secure context'); } - 不要试图用
iframe加载非安全源来绕过限制——现代浏览器会阻止此类降级行为 - 某些硬件 API(如 WebHID)还要求页面已获得用户至少一次明确授权,后续调用才可免交互,这点需在文档中明确提示用户首次操作路径
真正难的不是写 customElements.define,而是把浏览器对硬件的零容忍策略(权限、手势、上下文)准确映射到组件的生命周期和属性设计中。漏掉一次 catch、少一个 user-gesture 触发点,整个封装就失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











