最近在开发一个趣味性小应用时想实现一个类似“摇一摇”抽奖或互动的功能既能增加用户参与感又能作为技术练手。网上关于“摇一摇”的实现方案很多但大多集中在移动端原生开发对于想用Web技术快速实现一个轻量级、可嵌入H5页面或小程序的活动页面的开发者来说资料比较零散。本文将围绕“来杯萌茶摇一摇”这个趣味主题完整拆解如何使用HTML5、JavaScript和CSS3从零构建一个可交互的“摇一摇”动画效果并集成简单的抽奖逻辑。内容涵盖设备运动传感器DeviceMotionEvent的调用、动画帧渲染、状态管理以及结果展示的全流程。无论你是前端新手想学习传感器API还是需要为营销活动快速搭建一个互动页面的开发者都能从本文获得可直接复用的代码和清晰的实现思路。1. 背景与核心概念“摇一摇”交互已经成为移动端应用和网页中常见的趣味功能它利用手机内置的加速度计和陀螺仪检测用户摇晃设备的动作从而触发预设的响应如抽奖、切换内容、刷新页面等。1.1 什么是DeviceMotionEvent在Web开发中实现“摇一摇”功能的核心是DeviceMotionEventAPI。这是一个HTML5事件提供了设备在三维空间中的加速度和旋转速率信息。当设备移动时浏览器会触发此事件并携带详细的运动数据。加速度 (acceleration):包含x,y,z三个属性表示设备在各个轴向上的加速度包括重力加速度单位是 m/s²。重力加速度 (accelerationIncludingGravity):同样包含x,y,z属性表示包含重力影响的加速度。旋转速率 (rotationRate):包含alpha(绕Z轴旋转)、beta(绕X轴旋转)、gamma(绕Y轴旋转) 三个属性表示设备的旋转角速度单位是 °/s。1.2 “摇一摇”的检测原理我们并非直接使用原始的加速度数据而是通过计算一段时间内加速度变化的强度通常计算合加速度的差值来判断用户是否做出了“摇晃”动作。基本思路是监听devicemotion事件获取连续的加速度数据。计算当前时刻的合加速度三个轴加速度的平方和开根号。与上一时刻的合加速度进行比较计算差值。当差值超过设定的阈值并且在一定时间窗口内达到一定次数时即判定为一次有效的“摇一摇”动作。1.3 应用场景营销活动页面:如“摇一摇抽奖”、“摇一摇获取优惠券”。工具类应用:如“摇一摇随机选人”、“摇一摇切换主题”。游戏互动:作为游戏中的一种操作输入方式。内容刷新:类似很多资讯App的“摇一摇刷新”功能。2. 环境准备与版本说明本项目是一个纯前端项目无需后端服务或复杂的构建工具。核心依赖是现代浏览器对相关API的支持。运行环境:主要面向移动端浏览器iOS Safari, Android Chrome等部分API在PC端浏览器可能有限制或模拟数据。核心技术:HTML5, CSS3, JavaScript (ES6)关键API:DeviceMotionEvent,requestAnimationFrame开发工具:任意代码编辑器如VSCode、WebStorm即可。测试建议使用真机或浏览器的移动设备模拟器需开启传感器模拟。项目结构:一个简单的单HTML文件项目包含CSS和JS。shake-tea-demo/ ├── index.html # 主页面结构 ├── style.css # 样式文件 └── script.js # 交互逻辑文件版本注意:DeviceMotionEventAPI需要用户授权在iOS Safari上要求页面运行在HTTPS环境下或本地localhost环境。不同浏览器和操作系统版本对API的支持度和精度可能有差异本文代码将包含基本的兼容性处理和用户提示。3. 核心原理与API拆解3.1 监听设备运动事件首先需要向浏览器申请监听设备运动事件的权限并添加事件监听器。// 检查浏览器是否支持 DeviceMotionEvent if (window.DeviceMotionEvent) { // 请求权限在某些浏览器中事件监听本身会触发权限请求弹窗 window.addEventListener(devicemotion, handleDeviceMotion); } else { alert(抱歉您的设备或浏览器不支持摇一摇功能。); } // 事件处理函数 function handleDeviceMotion(event) { // 获取加速度数据包含重力 let acceleration event.accelerationIncludingGravity; let x acceleration.x; let y acceleration.y; let z acceleration.z; // 后续处理逻辑... }3.2 计算摇晃强度与阈值判断获取到加速度数据后需要计算摇晃强度并进行判断。let lastUpdate 0; // 上次更新时间戳 let shakeThreshold 15; // 摇晃强度阈值经验值可调整 let lastX, lastY, lastZ; // 上一次的加速度值 function handleDeviceMotion(event) { let current event.accelerationIncludingGravity; let currentTime new Date().getTime(); let timeDiff currentTime - lastUpdate; // 控制计算频率避免过于频繁例如每秒100次 if (timeDiff 10) { let deltaX Math.abs(lastX - current.x); let deltaY Math.abs(lastY - current.y); let deltaZ Math.abs(lastZ - current.z); // 判断三个轴向的变化是否有一个超过了阈值 if ((deltaX shakeThreshold deltaY shakeThreshold) || (deltaX shakeThreshold deltaZ shakeThreshold) || (deltaY shakeThreshold deltaZ shakeThreshold)) { // 触发摇晃成功逻辑 onShakeSuccess(); } // 更新上一次的数据和时间 lastX current.x; lastY current.y; lastZ current.z; lastUpdate currentTime; } }关键参数解释:shakeThreshold: 摇晃灵敏度阈值。值越小越敏感容易误触发值越大则需要更用力的摇晃。通常需要根据实际测试调整。timeDiff 10: 这是一个简单的节流控制确保每10毫秒最多计算一次避免devicemotion事件触发过于频繁导致性能问题。3.3 使用requestAnimationFrame实现动画为了在用户摇晃时提供视觉反馈比如茶杯晃动我们需要使用requestAnimationFrame来制作流畅的CSS动画或Canvas动画。let animationId null; let teaCupElement document.getElementById(tea-cup); function startShakeAnimation() { let startTime null; const duration 500; // 动画持续500毫秒 function animate(time) { if (!startTime) startTime time; const elapsed time - startTime; const progress Math.min(elapsed / duration, 1); // 进度 0~1 // 使用正弦函数模拟来回摇晃的效果 const shakeIntensity 20; const rotation Math.sin(progress * Math.PI * 4) * shakeIntensity; // 摇晃4个周期 teaCupElement.style.transform rotate(${rotation}deg); if (progress 1) { animationId requestAnimationFrame(animate); } else { // 动画结束复位 teaCupElement.style.transform rotate(0deg); animationId null; } } animationId requestAnimationFrame(animate); }4. 完整实战案例“来杯萌茶摇一摇”下面我们整合以上知识点构建一个完整的“摇一摇随机送一杯萌茶”的互动页面。4.1 创建项目结构与HTML创建index.html文件构建基本的页面结构。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0, user-scalableno title来杯萌茶摇一摇/title link relstylesheet hrefstyle.css link relstylesheet hrefhttps://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css /head body div classcontainer header h1i classfas fa-mug-hot/i 来杯萌茶摇一摇/h1 p classsubtitle摇晃手机随机解锁一杯特色萌茶/p /header main !-- 茶杯展示区 -- div classtea-cup-container div classtea-cup idtea-cup div classcup-body div classtea-liquid idtea-liquid/div div classcup-handle/div /div div classcup-saucer/div /div div classsteam idsteam/div /div !-- 状态提示 -- div classstatus idstatus p请用力摇晃您的手机.../p /div !-- 结果展示区 -- div classresult idresult h3您摇到了/h3 div classtea-card idtea-card h4 idtea-name--/h4 p idtea-desc等待摇晃揭晓/p div classtea-icon idtea-icon/div /div /div !-- 操作按钮 -- div classcontrols button idshake-btn classbtn shake-btni classfas fa-redo/i 模拟摇一摇/button button idreset-btn classbtn reset-btni classfas fa-undo/i 重置/button /div !-- 说明 -- div classinstructions h4i classfas fa-info-circle/i 使用说明/h4 ul li在手机浏览器中打开此页面。/li li当系统询问“是否允许访问设备运动与方向”时请点击strong允许/strong。/li li用力摇晃手机即可随机获得一杯萌茶。/li li也可点击“模拟摇一摇”按钮进行测试。/li /ul /div /main /div script srcscript.js/script /body /html4.2 添加CSS样式创建style.css文件为页面添加可爱的“萌系”样式。* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Segoe UI, Microsoft YaHei, sans-serif; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; color: #333; } .container { background-color: rgba(255, 255, 255, 0.95); border-radius: 30px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); padding: 30px; max-width: 500px; width: 100%; text-align: center; } header h1 { color: #ff6b8b; margin-bottom: 10px; font-size: 2.2rem; } .subtitle { color: #666; margin-bottom: 30px; font-size: 1.1rem; } /* 茶杯样式 */ .tea-cup-container { position: relative; margin: 40px auto; height: 250px; } .tea-cup { position: absolute; left: 50%; transform: translateX(-50%); transition: transform 0.1s ease-out; } .cup-body { width: 150px; height: 120px; background: linear-gradient(to right, #fff 0%, #f8f8f8 100%); border-radius: 0 0 70px 70px; position: relative; border: 8px solid #ffccd5; border-top: none; box-shadow: inset 0 0 10px rgba(0,0,0,0.05); } .tea-liquid { position: absolute; bottom: 0; width: 100%; border-radius: 0 0 60px 60px; transition: height 0.5s ease, background 0.5s ease; } .cup-handle { position: absolute; right: -30px; top: 30px; width: 50px; height: 70px; border: 8px solid #ffccd5; border-left: none; border-radius: 0 25px 25px 0; } .cup-saucer { width: 180px; height: 20px; background: #ffccd5; border-radius: 50%; position: absolute; top: 120px; left: -15px; box-shadow: 0 5px 15px rgba(0,0,0,0.1); } /* 水蒸气动画 */ .steam { position: absolute; top: -50px; left: 50%; opacity: 0; } .steam::before, .steam::after { content: ; position: absolute; background: rgba(255, 255, 255, 0.7); border-radius: 50%; animation: steamFlow 2s infinite linear; } .steam::before { width: 25px; height: 25px; left: -30px; animation-delay: 0s; } .steam::after { width: 20px; height: 20px; left: 10px; animation-delay: 0.5s; } keyframes steamFlow { 0% { top: 0; opacity: 0; transform: scale(0.5); } 50% { opacity: 0.8; } 100% { top: -80px; opacity: 0; transform: scale(1.2); } } /* 状态与结果 */ .status { background: #e9f5ff; padding: 15px; border-radius: 15px; margin: 25px 0; font-size: 1.2rem; color: #0066cc; border-left: 5px solid #4dabf7; } .result { background: #fff9db; padding: 20px; border-radius: 20px; margin: 25px 0; border: 2px dashed #ffd43b; display: none; /* 初始隐藏 */ } .tea-card { padding: 15px; } .tea-card h4 { font-size: 1.8rem; color: #e67700; margin-bottom: 10px; } .tea-card p { color: #666; line-height: 1.6; } .tea-icon { font-size: 3rem; margin-top: 15px; color: #ff6b8b; } /* 按钮 */ .controls { display: flex; gap: 20px; justify-content: center; margin: 30px 0; } .btn { padding: 15px 30px; border: none; border-radius: 50px; font-size: 1.1rem; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 10px; transition: all 0.3s ease; } .shake-btn { background: linear-gradient(to right, #40c4ff, #00b0ff); color: white; flex: 2; } .reset-btn { background: #f1f3f5; color: #495057; flex: 1; } .btn:hover { transform: translateY(-3px); box-shadow: 0 7px 14px rgba(0, 0, 0, 0.1); } .btn:active { transform: translateY(-1px); } /* 说明区域 */ .instructions { background: #f8f9fa; padding: 20px; border-radius: 15px; text-align: left; margin-top: 30px; border-left: 5px solid #adb5bd; } .instructions h4 { color: #495057; margin-bottom: 10px; } .instructions ul { padding-left: 20px; color: #666; } .instructions li { margin-bottom: 8px; line-height: 1.5; }4.3 编写核心JavaScript逻辑创建script.js文件实现摇晃检测、动画、抽奖逻辑和UI交互。// script.js document.addEventListener(DOMContentLoaded, function() { // 获取DOM元素 const teaCup document.getElementById(tea-cup); const teaLiquid document.getElementById(tea-liquid); const steam document.getElementById(steam); const statusEl document.getElementById(status); const resultEl document.getElementById(result); const teaNameEl document.getElementById(tea-name); const teaDescEl document.getElementById(tea-desc); const teaIconEl document.getElementById(tea-icon); const shakeBtn document.getElementById(shake-btn); const resetBtn document.getElementById(reset-btn); // 状态变量 let isShaking false; let isShakeEnabled false; let lastUpdate 0; let lastX null, lastY null, lastZ null; const SHAKE_THRESHOLD 18; // 摇晃阈值 const SHAKE_COOLDOWN 1500; // 摇晃冷却时间毫秒防止连续触发 let lastShakeTime 0; // 萌茶数据池 const teaMenu [ { name: 樱花奶绿, desc: 春日限定淡淡樱花香与醇厚奶绿融合口感绵密。, color: #ffb7c5, icon: }, { name: 波波芋泥, desc: 手作芋泥搭配Q弹波波饱腹感与幸福感双重满足。, color: #bb86fc, icon: }, { name: 芝士芒芒, desc: 浓郁芝士奶盖与鲜榨芒果冰沙是夏天的味道, color: #ffd166, icon: }, { name: 黑糖珍珠, desc: 经典永不过时焦香黑糖挂壁珍珠软糯有嚼劲。, color: #6d4c41, icon: }, { name: 薄荷柠茶, desc: 清爽薄荷与柠檬的碰撞一口提神赶走所有疲惫。, color: #a7e6c2, icon: }, { name: 椰椰乌龙, desc: 清甜椰乳与焙火乌龙的完美结合口感层次丰富。, color: #f4f1de, icon: } ]; // 初始化 initShakeDetection(); setupEventListeners(); // 初始化摇晃检测 function initShakeDetection() { if (typeof DeviceMotionEvent ! undefined typeof DeviceMotionEvent.requestPermission function) { // iOS 13 需要显式请求权限 statusEl.innerHTML pi classfas fa-hand-point-up/i 点击“模拟摇一摇”按钮或摇晃手机以开启权限/p; isShakeEnabled false; } else if (window.DeviceMotionEvent) { // 其他支持DeviceMotionEvent的浏览器 startWatchingShake(); isShakeEnabled true; } else { statusEl.innerHTML p stylecolor:#e03131;i classfas fa-exclamation-triangle/i 您的浏览器不支持摇一摇功能。/p; isShakeEnabled false; } } // 开始监听摇晃 function startWatchingShake() { window.addEventListener(devicemotion, handleDeviceMotion); statusEl.innerHTML pi classfas fa-mobile-alt/i 传感器已就绪请摇晃手机/p; } // 设备运动事件处理 function handleDeviceMotion(event) { if (!isShakeEnabled || isShaking) return; let current event.accelerationIncludingGravity; let currentTime new Date().getTime(); let timeDiff currentTime - lastUpdate; if (timeDiff 10) { // 节流 if (lastX null) { lastX current.x; lastY current.y; lastZ current.z; return; } let deltaX Math.abs(lastX - current.x); let deltaY Math.abs(lastY - current.y); let deltaZ Math.abs(lastZ - current.z); // 判断是否为有效摇晃两个轴向变化超过阈值 if ((deltaX SHAKE_THRESHOLD deltaY SHAKE_THRESHOLD) || (deltaX SHAKE_THRESHOLD deltaZ SHAKE_THRESHOLD) || (deltaY SHAKE_THRESHOLD deltaZ SHAKE_THRESHOLD)) { // 冷却时间检查 if (currentTime - lastShakeTime SHAKE_COOLDOWN) { lastShakeTime currentTime; triggerShake(); } } lastX current.x; lastY current.y; lastZ current.z; lastUpdate currentTime; } } // 触发摇晃成功 function triggerShake() { if (isShaking) return; isShaking true; // 1. 更新状态 statusEl.innerHTML pi classfas fa-star/i 摇到啦正在为您沏茶.../p; // 2. 播放茶杯摇晃动画 playShakeAnimation(); // 3. 延迟后显示结果模拟网络请求或处理时间 setTimeout(() { showRandomTea(); isShaking false; // 显示水蒸气动画 steam.style.opacity 1; setTimeout(() { steam.style.opacity 0; }, 2000); }, 800); } // 播放摇晃动画 function playShakeAnimation() { let start null; const duration 600; function step(timestamp) { if (!start) start timestamp; const progress timestamp - start; const percent Math.min(progress / duration, 1); // 使用正弦函数模拟前后摇晃 const shakeRange 25; const rotation Math.sin(percent * Math.PI * 6) * shakeRange; // 快速摇晃6个周期 teaCup.style.transform translateX(-50%) rotate(${rotation}deg); // 液体晃动效果 const waveHeight 10; teaLiquid.style.height ${70 Math.sin(percent * Math.PI * 8) * waveHeight}%; if (percent 1) { requestAnimationFrame(step); } else { // 动画结束复位 teaCup.style.transform translateX(-50%) rotate(0deg); teaLiquid.style.height 70%; } } requestAnimationFrame(step); } // 随机展示一杯茶 function showRandomTea() { const randomIndex Math.floor(Math.random() * teaMenu.length); const selectedTea teaMenu[randomIndex]; // 更新UI teaNameEl.textContent selectedTea.name; teaDescEl.textContent selectedTea.desc; teaIconEl.textContent selectedTea.icon; teaLiquid.style.backgroundColor selectedTea.color; teaLiquid.style.backgroundImage radial-gradient(circle at 30% 20%, ${selectedTea.color}99, ${selectedTea.color}); // 显示结果区域 resultEl.style.display block; statusEl.innerHTML pi classfas fa-check-circle/i 您的${selectedTea.name}已送达/p; } // 设置事件监听器 function setupEventListeners() { // 模拟摇一摇按钮 shakeBtn.addEventListener(click, function() { if (!isShakeEnabled typeof DeviceMotionEvent.requestPermission function) { // iOS 请求权限 DeviceMotionEvent.requestPermission() .then(permissionState { if (permissionState granted) { isShakeEnabled true; startWatchingShake(); triggerShake(); // 请求成功后自动触发一次 } else { statusEl.innerHTML p stylecolor:#e03131;i classfas fa-ban/i 未获得传感器权限无法使用摇一摇功能。/p; } }) .catch(console.error); } else if (isShakeEnabled) { triggerShake(); } else { alert(请确保在移动设备上访问此页面并允许运动传感器权限。); } }); // 重置按钮 resetBtn.addEventListener(click, function() { resultEl.style.display none; teaLiquid.style.height 70%; teaLiquid.style.backgroundColor #c3b091; teaLiquid.style.backgroundImage none; statusEl.innerHTML pi classfas fa-mobile-alt/i 传感器已就绪请摇晃手机/p; if (!isShakeEnabled window.DeviceMotionEvent) { initShakeDetection(); } }); } });4.4 运行与验证将上述三个文件index.html,style.css,script.js放在同一目录下。在手机浏览器中打开index.html文件或者使用电脑浏览器的移动设备模拟器如Chrome DevTools的Device Mode。首次访问时浏览器可能会弹出“是否允许访问设备运动与方向”的权限请求必须点击“允许”。用力摇晃手机观察茶杯动画和随机出现的萌茶结果。也可以点击“模拟摇一摇”按钮进行测试在iOS上会先触发权限请求。4.5 预期效果UI展示:页面中央有一个可爱的茶杯下方有状态提示和结果展示区。摇晃触发:当用户摇晃手机达到一定强度时茶杯会播放一个左右摇晃、液体波动的动画。结果生成:动画结束后会随机从“萌茶菜单”中选取一款显示其名称、描述、图标并改变茶杯中液体的颜色。水蒸气效果:结果出现时茶杯上方会有短暂的水蒸气动画。交互按钮:提供“模拟摇一摇”按钮用于测试和iOS权限请求和“重置”按钮。5. 常见问题与排查思路在实际部署和测试中你可能会遇到以下问题问题现象可能原因解决思路页面打开后没有任何反应摇晃手机无效。1. 浏览器不支持DeviceMotionEvent。2. 未授予运动传感器权限尤其是iOS Safari。3. 页面未通过HTTPS或localhost访问iOS要求。1. 检查浏览器控制台是否有错误。使用if (window.DeviceMotionEvent)判断支持性。2. 点击“模拟摇一摇”按钮主动触发iOS的权限弹窗。确保点击“允许”。3. 确保在HTTPS环境或localhost下运行。摇晃触发过于灵敏或过于迟钝。SHAKE_THRESHOLD阈值设置不合理。调整script.js中的SHAKE_THRESHOLD常量。值越小越敏感如10值越大需要更用力如25。需在真机上反复测试找到最佳值。在PC浏览器上测试摇晃无效。PC设备通常没有加速度计devicemotion事件可能返回null或模拟数据。使用浏览器的移动设备模拟器并开启传感器模拟Chrome DevTools - Sensors - Emulate device motion。主要依赖“模拟摇一摇”按钮进行PC端测试。动画卡顿或不流畅。devicemotion事件触发频率过高或requestAnimationFrame动画计算过于复杂。代码中已通过timeDiff 10进行节流。确保动画函数playShakeAnimation内的计算量小且使用requestAnimationFrame。结果区域不显示或显示错误。JavaScript逻辑错误或DOM元素ID不匹配。打开浏览器开发者工具Console检查是否有JS报错。确认script.js中获取的DOM元素ID与index.html中的完全一致。iOS上第一次点击按钮后后续摇晃依然无效。可能权限请求逻辑有误或事件监听器未正确绑定。检查initShakeDetection和startWatchingShake函数是否在权限授予后被正确调用。确保isShakeEnabled状态变量被正确更新。6. 最佳实践与工程建议将“摇一摇”功能投入实际项目时应考虑以下方面以提升稳定性、用户体验和可维护性。6.1 权限管理与用户体验渐进增强与优雅降级:始终检测API支持情况。对于不支持的浏览器提供友好的说明文字或替代交互方式如点击按钮。清晰的权限引导:在iOS上DeviceMotionEvent.requestPermission()必须在用户手势如点击触发的事件处理函数中调用。设计清晰的UI引导用户进行首次点击授权。权限状态持久化:可以考虑使用localStorage记录用户是否已拒绝权限避免每次页面加载都弹出令人厌烦的引导。6.2 性能优化事件节流:务必对devicemotion事件进行节流如示例中的timeDiff 10这个事件每秒可能触发数十次不必要的处理会消耗电量并可能导致卡顿。动画优化:使用CSStransform和opacity属性制作动画这些属性可由GPU加速。避免在动画循环中修改left,top等触发布局重排的属性。及时清理:在页面隐藏visibilitychange事件或用户跳转时移除事件监听器并取消动画帧释放资源。6.3 摇晃算法增强更稳定的算法:示例中的双轴阈值判断比较简单。更稳定的算法可以计算合加速度的变化量Math.sqrt(deltaX^2 deltaY^2 deltaZ^2)并与一个总阈值比较同时结合时间窗口内超过阈值的次数来判定。防抖与冷却:必须设置冷却时间如示例中的SHAKE_COOLDOWN防止一次剧烈摇晃被误判为多次触发。这对于抽奖等关键操作尤为重要。6.4 代码组织与扩展模块化:如果项目复杂可将摇晃检测器ShakeDetector、动画管理器AnimationManager和业务逻辑TeaLottery分离成独立模块或类。配置化:将阈值、冷却时间、动画参数等抽离为配置对象便于在不同环境或活动中调整。易于扩展:“萌茶”数据可以改为从后端API动态获取。摇晃触发后的逻辑triggerShake可以定义成回调函数或发布订阅模式方便接入不同的业务如抽奖、签到、刷新。6.5 生产环境注意事项HTTPS:确保生产环境使用HTTPS这是许多现代Web API包括运动传感器的要求。日志与监控:在关键节点如权限获取成功/失败、摇晃触发、结果请求添加日志便于问题排查。A/B测试:对于阈值等参数可以设计A/B测试根据用户实际交互数据调整到最佳值。降级方案:始终准备一个降级方案例如当摇晃功能不可用时显眼地提供一个“点击抽奖”按钮。通过以上步骤你不仅实现了一个有趣的“摇一摇”互动页面更掌握了移动端Web传感器开发的核心流程、常见坑点以及优化思路。