uni.ongyroscopechange在app端为空实现,h5和小程序也不可靠;真机需native.js调android sensormanager或ios cmmotionmanager,注意权限、初始化时机、传感器检测、单位换算、防抖动及资源释放。

uni.onGyroscopeChange 在 App 端根本不会触发
别折腾 uni.startGyroscope 了——它在 Android/iOS App 端是空实现,调用后既不报错也不回调,uni.onGyroscopeChange 永远收不到数据。这不是你漏了权限或没写对生命周期,是 uni-app 官方压根没在原生层接入陀螺仪。H5 和小程序端同样不可靠:H5 受限于浏览器策略,多数不暴露角速度;小程序平台(如微信)压根没开放该能力。真正能拿到实时角速度的,只有 App 端 + 原生能力补全这一条路。
Android 必须用 Native.js 调 SensorManager
Native.js 是最轻量、最快验证的方式,适合需要快速落地晃动控制的场景。关键点不是“能不能读”,而是“读得稳不稳”:
- 必须等
plus.ready触发后再初始化传感器,否则plus.android.runtimeMainActivity()可能为 null - 要显式检查设备是否支持:
sensorManager.getDefaultSensor(3)返回null就代表无硬件(常见于低端平板、部分华为旧机型) - Android 12+ 强制要求动态申请
BODY_SENSORS权限,仅ACCESS_FINE_LOCATION不起作用 - 原始
values是 rad/s 单位的三元组,x/y/z 分别对应绕设备三轴的旋转速率,直接映射到 CSS transform 时需做缩放(例如 ×10)才能产生肉眼可见的晃动感
示例片段(仅核心逻辑):
const main = plus.android.runtimeMainActivity();
const Context = plus.android.importClass('android.content.Context');
const SensorManager = plus.android.importClass('android.hardware.SensorManager');
const sensorManager = main.getSystemService(Context.SENSOR_SERVICE);
const gyroSensor = sensorManager.getDefaultSensor(3); // TYPE_GYROSCOPE
if (!gyroSensor) { console.warn('no gyro sensor'); return; }
const listener = plus.android.implements('android.hardware.SensorEventListener', {
onSensorChanged: function(event) {
const values = event.plusGetAttribute('values'); // [x, y, z]
// → 传给 Vue data 或触发自定义事件
}
});
sensorManager.registerListener(listener, gyroSensor, SensorManager.SENSOR_DELAY_GAME);
iOS 需桥接 CMMotionManager 且必须填 NSMotionUsageDescription
iOS 的陀螺仪路径更严格:必须用 Native.js 调 CMMotionManager,且 Info.plist 里缺 NSMotionUsageDescription 字段会导致初始化直接失败(不是静默,而是 native 层抛异常)。另外注意:
- 不能在页面
onLoad阶段就启动,要等plus.ready+ 用户首次交互(比如点击按钮)之后才安全 - iOS 的采样稳定性普遍优于 Android,但默认频率较低;若需更高响应,得手动 set
gyroUpdateInterval并 handlegyroData回调 - 原始数据单位也是 rad/s,但 iOS 的噪声更小,可减少滤波开销;不过仍建议对连续 2–3 帧做简单滑动平均,避免 UI 抖动过碎
用角速度值驱动 CSS 晃动要防“抖动过载”
直接把 raw x/y 值塞进 transform: rotateX(...deg) rotateY(...deg) 会非常诡异——人眼对高频微小旋转极其敏感,未加约束极易引发眩晕感。实际做法是:
- 只取 x/y 两轴(z 轴旋转对页面元素晃动贡献小,且易与用户自然翻转混淆)
- 对原始值做线性映射:比如
rotateX: Math.min(8, Math.max(-8, values[0] * 15)) + 'deg',硬限幅比 CSS transition 更可控 - 加 50–100ms 的 requestAnimationFrame 节流,避免每帧都重绘(Native.js 回调频率可能达 100Hz,但 UI 渲染 60fps 足够)
- 用户离开页面时,务必调用
sensorManager.unregisterListener(Android)或motionManager.stopGyroUpdates()(iOS),否则后台持续耗电
真正难的不是“怎么动”,而是“什么时候停”——陀螺仪数据不会自动归零,松手后仍有残余震荡。得结合低通滤波或静止检测(连续 500ms 内 |x|+|y|











