跨屏智能家居体验:手表、手机与中控屏的界面协同设计

发布时间:2026/7/27 0:40:35
跨屏智能家居体验:手表、手机与中控屏的界面协同设计 跨屏智能家居体验手表、手机与中控屏的界面协同设计一、引子同一个智能家居三套不同的交互语言打开 Apple Watch 上的家庭 App——一个简单的图标网格每页 6 个设备点击切换开关状态。打开 iPhone 上的家庭 App——设备卡片列表支持长按展开详细控制。走到客厅的中控屏iPad 挂在墙上——Scenes 优先、摄像头预览占半屏、设备按房间分组。同一个智能家居系统三个屏幕三套完全不同的交互范式。这种差异化是正确的还是偷懒的答案取决于一个核心判断差异是否服务于使用场景还是仅仅因为不同端的开发团队各自为政。手表端的使用场景是快速看一眼 简单操作出门时看一眼门锁是否锁好进卧室前调暗灯光。手机端的使用场景是完整操作 场景配置创建新的自动化规则调整设备参数。中控屏的使用场景是家庭总览 访客共享客人来时切换场景全家监控总览。跨屏协同的真正挑战不在于代码复用率而在于交互语法的统一性。设备卡片的颜色、图标、状态指示器在三个屏幕上应该保持视觉一致——即使布局不同识别性不能丢。二、底层机制三级响应式信息架构跨屏设计需要针对不同屏幕尺寸定义不同的信息密度和交互模式而非简单缩放。关键设计原则手表端信息密度最低但操作速度最快。核心场景是看一眼glanceable。界面上永远只显示当前最相关的 3-6 个设备其余通过轮盘式滑动访问。每个设备的控制限于一个动作——开关、调亮度、模式切换——不做多层级嵌套。手机端信息密度最高操作颗粒度最细。核心场景是管理manageable。可以创建场景、编辑自动化、调整每个设备的全部参数。按房间分组 收藏夹置顶是手机端最优的信息架构。中控屏信息广度最大操作面向多人。核心场景是共享sharable。需要同时展示多个房间的状态分屏/画中画、支持访客无需登录即可操作访客模式、保留操作历史便于回溯。三、生产级代码跨屏响应式组件系统/** * 跨屏智能家居组件系统 * * 核心理念同一份业务逻辑三套视觉呈现。 * 通过容器查询 设备能力检测自动切换组件变体。 */ // 屏幕断点定义 type ScreenClass watch | phone | tablet | panel; // 组件变体 type ComponentVariant compact | standard | expanded; // 设备能力 interface DeviceCapabilities { screenWidth: number; screenHeight: number; hasHover: boolean; // 是否支持悬停 hasMultiTouch: boolean; // 是否支持多点触控 isWallMounted: boolean; // 是否挂墙使用 inputMethod: touch | click | remote; } /** * 设备感知的组件上下文 * * 通过 Provider 模式注入到整个组件树 */ class DeviceContext { private static instance: DeviceContext; private capabilities: DeviceCapabilities; private resizeObserver: ResizeObserver | null null; private listeners: Set() void new Set(); private constructor() { this.capabilities this.detectCapabilities(); this.setupResizeObserver(); } static getInstance(): DeviceContext { if (!DeviceContext.instance) { DeviceContext.instance new DeviceContext(); } return DeviceContext.instance; } /** * 检测设备能力 * * 不使用 UserAgent 嗅探不可靠 * 而是检测实际渲染能力和交互方式 */ private detectCapabilities(): DeviceCapabilities { const width window.innerWidth; const height window.innerHeight; return { screenWidth: width, screenHeight: height, // CSS hover 媒体查询检测 hasHover: matchMedia((hover: hover)).matches, // 多点触控检测 hasMultiTouch: ontouchstart in window navigator.maxTouchPoints 1, // 中控屏特征横屏 大尺寸 固定位置 isWallMounted: width 768 width height, // 输入方式推测 inputMethod: this.detectInputMethod(), }; } /** * 推断输入方式 */ private detectInputMethod(): touch | click | remote { if (ontouchstart in window) return touch; // 电视/车机场景可能只有方向键 if (navigator.userAgent.includes(SmartTV)) return remote; return click; } /** * 获取屏幕类别 */ getScreenClass(): ScreenClass { const { screenWidth, screenHeight, isWallMounted } this.capabilities; if (screenWidth 200) return watch; if (screenWidth 430) return phone; if (isWallMounted screenWidth 768) return panel; return tablet; } /** * 获取对应组件的变体 */ getComponentVariant(): ComponentVariant { const screenClass this.getScreenClass(); const variantMap: RecordScreenClass, ComponentVariant { watch: compact, phone: standard, tablet: expanded, panel: expanded, }; return variantMap[screenClass]; } /** * 获取触摸目标最小尺寸 */ getMinTouchTarget(): number { const screenClass this.getScreenClass(); // 手表端可以略小旋转表冠辅助操作 // 中控屏需要更大手臂伸展操作精度降低 return screenClass watch ? 32 : screenClass panel ? 56 : 44; } getCapabilities(): DeviceCapabilities { return { ...this.capabilities }; } /** * 监听屏幕尺寸变化响应式 */ private setupResizeObserver(): void { this.resizeObserver new ResizeObserver(() { const oldClass this.getScreenClass(); this.capabilities this.detectCapabilities(); const newClass this.getScreenClass(); // 仅当屏幕类别变化时通知 if (oldClass ! newClass) { this.listeners.forEach((fn) fn()); } }); this.resizeObserver.observe(document.documentElement); } onScreenClassChange(listener: () void): () void { this.listeners.add(listener); return () this.listeners.delete(listener); } destroy(): void { this.resizeObserver?.disconnect(); this.listeners.clear(); } } /** * 跨屏设备卡片组件 */ class DeviceCard { private element: HTMLElement; private context: DeviceContext; private cleanup: (() void) | null null; constructor(container: HTMLElement, device: SmartDevice) { this.context DeviceContext.getInstance(); this.element document.createElement(div); this.element.className device-card; this.render(device); container.appendChild(this.element); // 监听屏幕类别变化自动切换变体 this.cleanup this.context.onScreenClassChange(() { this.render(device); }); } private render(device: SmartDevice): void { const variant this.context.getComponentVariant(); const cap this.context.getCapabilities(); this.element.className device-card device-card--${variant}; this.element.setAttribute(data-device-id, device.id); this.element.setAttribute(role, button); this.element.setAttribute(aria-label, ${device.name}当前${this.describeState(device)}); this.element.setAttribute(tabindex, 0); switch (variant) { case compact: this.renderCompact(device); break; case standard: this.renderStandard(device); break; case expanded: this.renderExpanded(device, cap); break; } } /** * 紧凑模式手表端 * - 仅显示图标 设备名可选 开/关状态 * - 整个卡片 一个开关按钮 * - 点击切换电源状态 */ private renderCompact(device: SmartDevice): void { const isOn device.state.power true; this.element.innerHTML div classcard-compact ${isOn ? is-on : is-off} span classcard-icon aria-hiddentrue ${this.getDeviceIcon(device.type)} /span span classcard-label${device.name.slice(0, 3)}/span /div ; this.element.addEventListener(click, () { this.toggleDevice(device); }); } /** * 标准模式手机端 * - 图标 设备名 状态简述 主操作 * - 长按展开详细控制面板 * - 滑动手势切换设备 */ private renderStandard(device: SmartDevice): void { const isOn device.state.power true; this.element.innerHTML div classcard-standard ${isOn ? is-on : is-off} div classcard-header span classcard-icon aria-hiddentrue ${this.getDeviceIcon(device.type)} /span div classcard-info h4 classcard-name${device.name}/h4 p classcard-status${this.describeState(device)}/p /div /div div classcard-controls button classpower-toggle ${isOn ? active : } aria-label${isOn ? 关闭 : 打开} ${device.name} span classtoggle-track/span span classtoggle-thumb/span /button /div /div ; // 长按展开详情 let longPressTimer: number; this.element.addEventListener(touchstart, () { longPressTimer window.setTimeout(() { this.showDetailPanel(device); }, 500); }); this.element.addEventListener(touchend, () { clearTimeout(longPressTimer); }); } /** * 扩展模式中控屏/平板 * - 全尺寸卡片 实时图表如能耗曲线 * - 支持悬停预览hover * - 支持拖拽排序和分组 */ private renderExpanded( device: SmartDevice, cap: DeviceCapabilities ): void { this.element.innerHTML div classcard-expanded div classcard-visual span classcard-icon-large aria-hiddentrue ${this.getDeviceIcon(device.type)} /span ${device.type thermostat ? canvas classtemp-chart/canvas : } /div div classcard-details h3 classcard-name${device.name}/h3 p classcard-room${device.room}/p div classcard-metrics ${this.renderMetrics(device)} /div /div div classcard-actions ${this.renderActions(device)} /div /div ; // 中控屏支持悬停 if (cap.hasHover) { this.element.addEventListener(mouseenter, () { this.showQuickPreview(device); }); } } private getDeviceIcon(type: string): string { const icons: Recordstring, string { light: , thermostat: ️, lock: , curtain: , speaker: , camera: , }; return icons[type] || ; } private describeState(device: SmartDevice): string { if (device.type light) { const brightness device.state.brightness; const colorTemp device.state.colorTemp; if (device.state.power false) return 已关闭; return ${brightness}% ${colorTemp ? · ${colorTemp}K : }; } if (device.type thermostat) { return ${device.state.temperature}°C ${device.state.mode}; } if (device.type lock) { return device.state.locked ? 已上锁 : 未上锁; } return device.state.power ? 运行中 : 已关闭; } private renderMetrics(device: SmartDevice): string { if (device.type thermostat) { return div classmetric span classmetric-value${device.state.temperature}°/span span classmetric-label当前温度/span /div div classmetric span classmetric-value${device.state.humidity || --}%/span span classmetric-label湿度/span /div div classmetric span classmetric-value${device.state.power_consumption || --}W/span span classmetric-label功耗/span /div ; } return ; } private renderActions(device: SmartDevice): string { const actions: Recordstring, string[] { light: [开关, 亮度, 色温, 场景], thermostat: [开关, 温度, 模式, 定时], lock: [上锁, 解锁, 查看记录], camera: [查看, 对讲, 回放], }; const deviceActions actions[device.type] || [开关]; return deviceActions .map( (a) button classaction-btn stylemin-height: ${this.context.getMinTouchTarget()}px${a}/button ) .join(); } private toggleDevice(device: SmartDevice): void { // 切换设备电源 const newState !device.state.power; console.log(Toggling ${device.id} to ${newState}); // 实际项目中通过 MQTT 下发指令 } private showDetailPanel(device: SmartDevice): void { // 展开详细控制面板底部抽屉或弹窗 } private showQuickPreview(device: SmartDevice): void { // 悬停预览中控屏鼠标悬停时显示缩略信息 } destroy(): void { this.cleanup?.(); this.element.remove(); } } // CSS: 跨屏响应式 const crossScreenStyles /* 基础设备卡片 */ .device-card { transition: all 0.3s ease; touch-action: manipulation; } /* 紧凑模式手表 */ .device-card--compact { width: 52px; height: 52px; border-radius: 16px; display: grid; place-items: center; } .device-card--compact .card-compact { display: flex; flex-direction: column; align-items: center; gap: 2px; } .device-card--compact .card-icon { font-size: 24px; } .device-card--compact .card-label { font-size: 10px; color: #666; } /* 标准模式手机 */ .device-card--standard { width: 100%; padding: 16px; border-radius: 16px; background: white; box-shadow: 0 2px 8px rgba(0,0,0,0.08); } .device-card--standard .card-header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; } /* 扩展模式中控屏/平板 */ .device-card--expanded { padding: 24px; border-radius: 20px; background: white; box-shadow: 0 4px 16px rgba(0,0,0,0.1); display: grid; grid-template-columns: 120px 1fr auto; gap: 24px; min-height: 120px; } /* 触摸目标保证 */ .device-card--expanded .action-btn { min-width: v-bind(minTouchTarget); min-height: v-bind(minTouchTarget); border-radius: 12px; border: 1px solid #e0e0e0; background: white; font-size: 14px; } /* 悬停效果仅中控屏 */ media (hover: hover) { .device-card--expanded:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.15); } } /* 焦点样式中控屏遥控器操作 */ .device-card:focus-visible { outline: 3px solid #1976D2; outline-offset: 3px; } ;四、边界分析手表端的极端限制Apple Watch Series 屏幕宽度只有 184px40mm或 198px44mm。52px 的图标卡片意味着一排最多放 3 个含间距。如果用户有 20 个设备翻页体验糟糕。解决方案是动态置顶——只显示最近操作的 6 个设备和 2 个最常用场景其余通过 Siri 语音调用。中控屏的访客模式中控屏是家庭中共用程度最高的设备。访客保姆、朋友、亲戚可能需要在没有账号的情况下操作设备。需要设计一个访客模式限制可操作设备范围如仅灯光和窗帘、禁止修改场景配置、操作记录可追溯。同步延迟的视觉处理手表上操作了灯光手机上看到灯光状态变化中控屏上也同步更新——这三者的端到端延迟在理想情况下应该 500ms。但在弱网环境下可能达到 3-5 秒。需要在 UI 上区分乐观更新立即显示预期状态和确认更新收到设备 ACK 后显示实际状态避免界面抖动。五、总结跨屏协同的核心是交互语法统一布局适应场景而非代码复用手表 概览模式一瞥一操作手机 标准模式完整管理中控屏 全景模式共享总览容器查询 设备能力检测比媒体查询更适合跨屏设备的差异化触摸目标应随屏幕尺寸变化手表 32px、手机 44px、中控屏 56px同一份业务数据三套视觉呈现——变体模式而非分支代码手表端最多展示 6 个相关设备中控屏需要访客权限隔离乐观更新 确认更新的双重策略应对跨屏状态同步延迟