Next.js 13移动端震动检测计数器功能实现报错求助
解决方案:Next.js 13 实现移动端震动计数功能
1. 解决SSR环境下的window对象与类型问题
Next.js 13 App Router默认是服务器端渲染,服务器端不存在window对象,且TypeScript默认类型定义中没有requestPermission方法,需要做以下处理:
- 在组件顶部添加
'use client'指令,确保代码仅在客户端执行; - 扩展全局类型,避免类型报错。
2. 兼容性处理权限请求
DeviceMotionEvent.requestPermission()是Safari(iOS/macOS)特有的API,其他主流浏览器(Chrome、Firefox等)无需主动请求权限,需做分支判断:
// 扩展全局类型,消除TypeScript报错 declare global { interface DeviceMotionEvent { requestPermission?: () => Promise<'granted' | 'denied'>; } } async function requestMotionPermission() { if (typeof window !== 'undefined' && 'requestPermission' in DeviceMotionEvent) { const permission = await (DeviceMotionEvent.requestPermission as () => Promise<'granted' | 'denied'>)(); return permission === 'granted'; } // 非Safari环境默认返回已授权 return true; }
3. 监听震动并实现计数逻辑
devicemotion事件会持续触发,需要设置加速度阈值判断“有效震动”,同时添加防抖避免重复计数:
'use client'; import { useState, useEffect } from 'react'; declare global { interface DeviceMotionEvent { requestPermission?: () => Promise<'granted' | 'denied'>; } } export default function MotionCounter() { const [count, setCount] = useState(0); let isShaking = false; const SHAKE_THRESHOLD = 15; // 可根据设备灵敏度调整阈值 useEffect(() => { async function setupMotionListener() { const hasPermission = await requestMotionPermission(); if (!hasPermission) return; function handleMotion(event: DeviceMotionEvent) { const acceleration = event.accelerationIncludingGravity; if (!acceleration) return; // 计算三维合加速度大小 const totalAcceleration = Math.sqrt( Math.pow(acceleration.x || 0, 2) + Math.pow(acceleration.y || 0, 2) + Math.pow(acceleration.z || 0, 2) ); // 防抖逻辑:避免短时间内重复计数 if (totalAcceleration > SHAKE_THRESHOLD && !isShaking) { isShaking = true; setCount(prev => prev + 1); setTimeout(() => { isShaking = false; }, 1000); } } window.addEventListener('devicemotion', handleMotion); return () => window.removeEventListener('devicemotion', handleMotion); } setupMotionListener(); }, []); async function requestMotionPermission() { if (typeof window !== 'undefined' && 'requestPermission' in DeviceMotionEvent) { const permission = await (DeviceMotionEvent.requestPermission as () => Promise<'granted' | 'denied'>)(); return permission === 'granted'; } return true; } return ( <div> <h2>震动次数:{count}</h2> </div> ); }
关键注意事项
- Safari要求权限请求必须由用户主动交互触发(比如按钮点击),直接在组件挂载时请求会被拒绝;
SHAKE_THRESHOLD值需根据测试调整,数值越大需要震动强度越高才会触发计数;- 必须在真实移动端设备上测试,模拟器无法准确模拟设备震动效果。
内容的提问来源于stack exchange,提问作者Ronald Ng
相关产品推荐
相关产品推荐

