
打开微信小程序开发者工具新建一个项目然后在搜索框里敲指南针你会看到一大堆功能几乎一模一样的小程序UI 却差别巨大。这个现象本身就说明了一件事指南针是小程序入门里最典型的麻雀虽小五脏俱全案例。它不像待办清单那样只是练练数据绑定也不像计算器那样纯拼逻辑它把设备传感器、Canvas 绘图、CSS 变换、生命周期管理、真机性能调优这几条线全串起来了代码量却不到三百行。我前后带过几个刚接触小程序的朋友第一个练手项目都是它因为跑通一遍之后你会对逻辑层和渲染层到底怎么配合这件事有非常具体的体感而不是停留在文档里的抽象描述。这篇文章我会把整个案例掰开揉碎从环境搭建、传感器原理、角度换算、刻度盘绘制一路讲到真机上的抖动、跳变、方向反向这些实际会咬人的问题。不管你是刚装完开发者工具的新手还是写过几个页面但没碰过设备接口的开发者照着走一遍都能拿到一个能装到手机上用的指南针顺便把小程序的基础设施摸个门清。1. 为什么拿指南针当第一个练手项目1.1 这个案例到底覆盖了哪些小程序核心能力很多人入门喜欢写个静态页面改改文字、点点按钮就结束了这种练习的收益极低因为它绕开了小程序最核心的那套通信机制。指南针不一样它的数据源是持续变化的每秒钟可能给你推十几次甚至几十次方向数据这就逼着你去思考一个问题数据从逻辑层流到渲染层中间要过一道桥这道桥是有成本的你不可能无脑地把每一次回调都塞进setData。具体来说一个完整的指南针小程序会让你接触到这些东西设备方向的订阅与取消订阅wx.startCompass/wx.stopCompass/wx.onCompassChange、页面生命周期与传感器生命周期的对齐、setData的频率控制、Canvas 2D 接口的节点查询与高分屏适配、CSStransform的旋转与性能特征、角度跨越 0/360 边界时的插值处理。这些知识点单独拿出来讲都很枯燥但塞进一个指南针里就变得非常自然因为它们都是为了解决指针转得顺不顺这一个具体问题而存在的。我一直觉得学技术最好的路径是先遇到问题再找工具而不是先背工具再找场景。指南针恰好提供了这样一个闭环你写完第一版发现指针卡顿才会去查节流你发现指针会突然倒转一圈才会去研究角度归一化你发现高分屏上刻度发虚才会去理解pixelRatio。每一个优化点背后都有一个真实的痛点在推着,这比起看文档记 API 的效率要高得多。1.2 和待办清单、计算器相比它的独特价值在哪待办清单练的是列表渲染、数据增删改、本地存储偏业务侧计算器练的是表达式解析、事件处理、布局对齐偏逻辑侧。这两个都属于纯软件范畴你写完之后对小程序的理解基本停留在它像一个简化版的网页这个层面。指南针则把你带进了设备能力这一层。小程序能拿到的设备数据其实相当有限罗盘、加速度计、陀螺仪、麦克风、摄像头掰着指头能数过来而罗盘是最容易上手、副作用最小的一个——它不需要用户授权弹窗这点非常关键位置、录音、摄像头都会弹权限框调试时很烦也不需要后台申请接口权限打开就能用。这一点对新手特别友好你不需要先去研究权限声明、隐私协议这些小程序的合规流程就能直接体会到设备数据流的真实手感。另外一个隐形价值是调试体验。指南针的输入来自物理世界开发者工具里的模拟器没法真的感知磁场所以微信官方在调试器里做了一个传感器面板你可以手动拖动来模拟方向。这个面板本身就是一个很好的教学工具它让你能直观地看到direction从 0 转到 359 再回到 0 的全过程比看日志数字要清楚得多。学会用这个面板之后你调试加速度计、陀螺仪都会轻松很多。1.3 上手前需要先明确的几个前提在动手之前有几个前提条件最好先确认清楚能省掉后面一堆莫名其妙的排查时间。第一是基础库版本指南针相关的接口从很早的版本就有了但wx.onCompassChange在新版本里增加了type参数用于区分低功耗和高频模式如果你的基础库太老某些参数会不生效建议开发时把基础库调到近两年的稳定版本在开发者工具的详情面板里可以设置。第二是调试环境的选择。开发者工具能跑通不代表真机能跑通传感器类项目尤其如此因为模拟器里的方向数据是你手动拖出来的它永远是平滑的而你手上的手机在真实磁场里读数会抖。所以从第一版开始就养成写完立刻真机预览的习惯扫码打开边走边看指针有没有跟上这一步不能省。第三是机型差异的心理准备。不同手机的磁力计精度、采样频率、系统层面的滤波策略都不一样同一份代码在 A 手机上丝滑在 B 手机上可能就有轻微延迟。这不是 bug是硬件层面的客观差异后面我会讲怎么用软件手段把这个差距抹平到一个可接受的范围内。2. 开发环境搭建与项目骨架初始化2.1 账号、AppID 与开发者工具的正确打开方式小程序的开发工具是免费的去官方文档站下载对应系统的安装包就行Windows 和 macOS 都有。安装完之后打开会要求扫码登录用你自己的微信号扫一下就进去了这一步不需要注册什么账号。真正需要账号的是 AppID。虽然开发者工具允许你点测试号或者不使用 AppID来创建项目但测试号有功能限制而且后面想真机预览的时候可能会遇到麻烦。所以我建议直接去小程序管理后台走一遍注册流程个人主体就能注册流程大概是填邮箱、收验证码、填主体信息、绑定微信号十来分钟能搞定。注册完之后在后台的开发管理里就能看到 AppID一串wx开头的字符。有一点很多人会卡住注册时填的邮箱必须是没注册过公众平台其他账号的邮箱如果你拿一个已经绑过公众号的邮箱去注册会直接报错。这个坑踩过的人不少提前准备一个干净邮箱就行。另外个人主体的小程序目前不能开通某些涉及交易的类目但做指南针这种工具类完全够用不用担心。开发者工具安装完成后首次打开建议在设置里把代理设置保持默认、安全设置里的选项按需开启其它保持默认即可。工具本身占内存不小如果你的机器配置一般可以关掉不用的模拟器机型只留一个 iPhone 和一个 Android 就够日常开发了。2.2 新建项目时几个容易被忽略的配置项点新建项目之后界面会让你填项目名称、目录、AppID然后选一个模板。模板这里建议直接选不使用模板或者JavaScript 基础模板不要选那些花里胡哨的示例模板因为示例模板会塞进来一堆你不需要的文件反而干扰你理解项目结构。有个选项叫是否使用云开发做指南针不需要果断关掉。云开发会初始化一堆云函数目录和配置文件对入门项目来说是纯噪音。另外一个容易被忽略的是后端服务选项选不使用云服务就行指南针是纯前端项目所有计算都在本地完成。项目创建好之后工具会自动生成一个最小可运行的项目包含app.js、app.json、app.wxss和一个pages/index目录。这时候别急着写代码先在工具里点一下编译确认能跑起来看到一个白底页面再开始动手改。这个先验证再修改的习惯能帮你排除掉环境本身的问题否则后面出了错你分不清是环境还是代码的锅。还有个小细节项目目录名最好不要用中文和空格路径里带中文在某些情况下会导致工具的文件监听出问题虽然不常见但避开了总没坏处。同理项目名称也别太长工具顶部标签栏的宽度是有限的。2.3 目录结构和页面四件套的分工小程序的一个页面由四个同名文件组成分别是.wxml、.wxss、.js、.json。很多人一开始会困惑为什么不用一个文件搞定其实这个拆分挺合理的.wxml负责结构相当于骨架.wxss负责样式相当于皮肤.js负责逻辑和数据相当于大脑.json负责这个页面的配置比如导航栏标题、是否允许下拉刷新。指南针这个项目最简结构就是一个页面pages/index加上全局的app.js、app.json、app.wxss。如果你想练得更完整一点可以加一个关于页面放说明文字用navigator组件跳转顺便熟悉一下页面栈的概念——不过对这个案例来说单页面完全够用。app.json里有几个字段值得顺手了解一下。window下面的navigationBarTitleText控制顶部标题文字把它设成指南针navigationBarBackgroundColor和navigationBarTextStyle控制导航栏配色注意navigationBarTextStyle只支持black和white两个值写别的颜色是不生效的这一点新手经常踩。如果你想让页面全屏显示把navigationStyle设成custom就能隐藏系统导航栏但那样你就得自己处理顶部安全区域新手阶段不建议先用默认导航栏把精力放在功能本身。等指针跑顺了再去折腾全屏沉浸式效果也不迟。3. 指南针背后的原理从磁场到指针角度3.1 手机是怎么感知方向的手机里有个叫磁力计的元件本质上是三个互相垂直的磁场传感器分别测量 X、Y、Z 三个轴向上的磁场强度单位通常是微特斯拉。地球本身有一个稳定的磁场在水平面上的分量总是指向磁北方向磁力计测出这个分量在 X 轴和 Y 轴的投影之后用反正切函数一算就能得到设备与磁北之间的夹角。用大白话说就是手机能感觉到哪边磁场更强而地球磁场在水平方向上始终指向北方所以它就知道北在哪边了。这个角度是相对于手机自身坐标系的你转动手机X、Y 轴上测到的分量就变了算出来的角度也跟着变指南针指针就这么动起来了。这里有个比较容易忽略的点手机算出来的是相对磁北的角度不是相对真北的。磁北和真北之间差一个磁偏角这个角度在中国不同地区从西偏几度到东偏几度不等具体数值跟经纬度有关。手机系统层面一般已经做了修正很多机型会根据定位信息自动补偿所以你在小程序里拿到的direction大概率已经接近真北了。如果做的是专业导航类应用这个偏差必须处理但作为一个入门案例直接用就行我会在后面提一句怎么判断是否需要修正。3.2 direction、accuracy 这两个返回值到底意味着什么wx.onCompassChange的回调里会给你一个对象主要就两个字段。direction是当前方向单位是度范围 0 到 360注意它是顺时针增加的——手机顶端指向正北时是 0向右转到正东是 90正南 180正西 270。这个方向和数学里的极坐标方向是反的数学里通常逆时针增加所以在做角度换算时要特别小心正负号。accuracy表示方向精度这个字段在不同基础库版本下类型不太一样老版本给的是high、medium、low、no这样的字符串档位新版本有的机型给的是数字表示误差角度。写代码的时候最稳妥的做法是用typeof判断一下类型是字符串就按档位展示成精度良好/一般/差是数字就直接取绝对值比阈值。这个字段非常有用因为磁场环境差的时候比如你站在变压器旁边、或者手机套了带磁吸的保护壳direction会乱跳这时候界面上提示用户请远离磁性物体比让指针疯转体验好得多。取数据之前要先调wx.startCompass页面不需要的时候调wx.stopCompass。这两个接口是配对使用的只注册监听不启动是拿不到数据的这个点很隐蔽我第一次写的时候盯着空白的日志看了半天才想起来。3.3 角度归一化和方向文字的换算从direction换到界面上的方位文字逻辑很简单把 360 度分成八个扇区每 45 度一个然后查表。正北是 0 度附近东北是 45 度附近以此类推。实现的时候用Math.round(direction / 45) % 8算出索引再从一个长度为 8 的数组里取文字就行。但要注意边界情况。当direction是 350 度时350 / 45约等于 7.78四舍五入是 8取模之后是 0正好对应正北这个是对的。当direction是 22 度时除下来约 0.49四舍五入是 0也是正北符合直觉。所以这个取模写法是可靠的。再进一步如果你想让方位显示更细腻一点可以用十六方位正北、北东北、东北、东东北……不过中文里这种叫法比较别扭一般工具类小程序用八方位就够了。真要做精细的可以直接显示角度数字比如东北 42°比纯文字信息量更大。还有个小技巧角度数字建议取整显示但内部计算保留小数因为小数点后两位的变化肉眼看不出来反而会让数字频繁跳动看起来像坏了。显示的时候用Math.round就行一秒钟变一两次数字是正常的一秒钟变十几次就太吵了。3.4 为什么指针旋转角度要取负值这是整个案例里最容易绕晕的地方。direction指的是手机顶端指向的方向单位是顺时针度数。而我们的刻度盘是固定在屏幕上的指针要指向北方。假设手机顶端指向正东也就是direction等于 90那北方就在手机屏幕的左边指针应该指向左边也就是相对初始位置逆时针转 90 度。CSS 的rotate正角度是顺时针旋转所以要让指针逆时针转 90 度就得传-90。结论就是transform: rotate(-direction deg)。听起来很绕但你在纸上画一个圈标上手机顶端方向和北方画两次就彻底明白了。还有一种做法是反过来让刻度盘跟着转指针固定朝上。这种设计在真实罗盘上也有叫转动式罗盘界面上看起来是刻度圈在转、指针不动。实现上就是rotate(direction deg)正号。两种方案视觉上完全不同选哪种看你喜好我倾向于固定刻度盘加旋转指针因为刻度盘上的 N/E/S/W 文字保持正立读起来更舒服。需要提醒的是如果你用 canvas 画整个盘然后旋转 canvas文字会跟着转转到 180 度时 N 是倒着的比较难看。如果一定要转盘可以在绘制每个文字的时候单独反向旋转回来但这样代码复杂度会上升。所以我的建议是canvas 只负责画静态刻度指针用一个单独的view做 CSS 旋转各管各的逻辑最清晰。4. 界面结构与样式实现的取舍4.1 WXML 的骨架怎么搭页面的结构其实非常简单三层就够了。最外层是一个容器负责整体居中和背景色中间是罗盘区域里面放一个 canvas 画刻度盘最上层是指针层绝对定位盖在 canvas 上跟着角度旋转。顺序很重要后面的元素会盖在前面上面所以 canvas 要先写指针后写。如果你把 canvas 写在后面它会盖住指针看起来就是指针不见了。这个错误我在教别人的时候见过好几次症状是指针不显示一查发现是层级问题。建议给外层容器加一个position: relative指针层用position: absolute加上top: 50%; left: 50%居中再用transform: translate(-50%, -50%)把自己的中心对准容器中心。这里有个细节transform-origin默认是元素的中心点如果你用translate平移之后旋转仍然是绕自身中心转的这正是我们想要的效果。但如果你不用 translate而是用负 margin 来居中旋转轴心可能就会偏指针会画出一个圈而不是原地转。在指针下面通常还会放一行文字显示当前方位和角度再加一个精度提示。这部分用普通的view和text就行位置放在罗盘下方留够间距。4.2 刻度盘用什么画Canvas 还是纯 CSS画刻度盘有三条路我一个个说。第一条是直接用一张设计好的图片当背景。优点是省事、好看、性能好缺点是尺寸固定换屏幕尺寸要重新切图而且每加一个刻度样式都得重新导出。适合快速验证不适合学习。第二条是纯 CSS 画。用一个view加border-radius: 50%做成圆然后往里塞几十个绝对定位的小view当刻度每个用transform: rotate定位。这条路能跑通但 DOM 节点数量会爆炸360 度每 5 度一个就 72 个节点再加上文字标签上百个节点低端机上渲染压力不小。第三条是 Canvas也是我推荐的。用 Canvas 2D 接口一次绘制几十条线和几个文字全在一个画布上节点数量是 1。绘制成本集中在页面加载时那一次之后画面不动完全不影响帧率。Canvas 方案的具体做法是给 canvas 加一个 id然后在onReady生命周期里用wx.createSelectorQuery()查询这个节点拿到真实的 canvas 对象和它的宽高然后按pixelRatio放大画布尺寸再调用ctx.scale(dpr, dpr)这样后续的坐标就都用逻辑像素表示了高分屏上线条也不会糊。注意Canvas 的节点查询必须放在onReady里做放在onLoad里会拿不到节点因为那时候 wxml 还没渲染完。这是新手最常见的报错来源之一报错信息通常是未找到节点或者查询结果为空数组。4.3 刻度与文字的绘制细节绘制逻辑核心就是一次循环从 0 度到 355 度每 5 度画一条刻度线。为了让 0 度朝上需要把角度做一次偏移先用(deg - 90) * Math.PI / 180把角度转成弧度减去 90 是因为 canvas 坐标系里 0 度是指向右方的而我们希望 0 度指向上方。刻度的长度要分三级这样看起来才有层次。整十度每 30 度画长线并且加粗比如 0、30、60 这些位置每 10 度画中等长度的线剩下的每 5 度画短线。北向的刻度用红色突出其他用深灰色。如果你还想更专业可以把东南西北四个正方向的刻度用更醒目的颜色比如北红、东蓝、南绿、西黄不过工具类应用通常只突出北方就够了。方位文字用ctx.fillText绘制字体设置在 14 到 16 像素之间比较合适太小了看不清太大了会跟刻度线打架。文字的位置要往圆心方向缩进一点一般缩进半径的0.75倍左右具体数值得试。文字的对齐方式设成center和middle这样给定坐标就是文字中心点定位方便。绘制顺序上有个小坑每次重绘之前一定要先clearRect清空整个画布否则旧的线条会叠在下面画面会越来越脏。如果你需要频繁重绘比如要画动态的偏差指示这个清空步骤绝对不能漏。4.4 尺寸适配与 rpx、px 的混用陷阱小程序的rpx是按 750 设计稿宽度做的自适应单位一个rpx在 iPhone 6 上等于 0.5 个物理像素。布局用rpx没问题但在 Canvas 里就麻烦了因为 canvas 的绘制坐标是逻辑像素不是rpx。常见的做法是wxml 里用rpx给 canvas 定尺寸查询节点的时候拿到的width和height是逻辑像素px然后把这个值直接用于绘制。这样虽然 canvas 的物理尺寸随屏幕变化但绘制坐标系是统一的不用做换算。再配合pixelRatio放大画布就能做到尺寸自适应 高分屏清晰。具体写法是先读wx.getWindowInfo().pixelRatio注意这个 API 取代了旧的wx.getSystemInfoSync新项目直接用新的然后设置canvas.width 逻辑宽 * dpr、canvas.height 逻辑高 * dpr最后ctx.scale(dpr, dpr)。提示canvas 元素的宽高属性和它的 CSS 宽高是两回事。前者决定画布有多少像素后者决定它显示多大。只改 CSS 不改属性画面会被拉伸模糊只改属性不改 CSS画面会超出容器。两个都要设而且要按 dpr 比例设。指针的尺寸建议也用rpx因为它是 CSS 元素rpx会自动适配。指针的宽度大概是罗盘直径的十分之一长度略小于半径这样看起来比例比较协调。如果你用三角形拼指针border的宽度也要用rpx否则在不同手机上比例会走形。5. 逻辑层实现订阅、节流与平滑5.1 生命周期里的订阅与释放传感器订阅的时机很讲究。onLoad里拿不到 canvas 节点但可以启动传感器onReady里可以拿节点但页面还没真正进入前台。稳妥的做法是在onReady里调用wx.startCompass并注册wx.onCompassChange在onUnload里调用wx.stopCompass并wx.offCompassChange注销监听。为什么要在onUnload里注销因为页面被销毁之后监听器如果还挂着回调仍然会被触发而回调里有可能去访问已经不存在的数据轻则报错刷屏重则内存泄漏。小程序里页面级的资源释放意识要早点建立起来不然项目做大了会很难查。还有一个容易被忽略的场景是后台切换。用户按了 Home 键小程序退到后台这时候传感器数据其实已经停了但你的监听器还在页面数据停留在最后一帧。等用户再切回来的时候你会发现指针卡住不动了需要在onShow里重新startCompass一次。反过来在onHide里调stopCompass是个好习惯能省电用户手机上那个某某小程序正在使用传感器的提示也会消失得更快。把这些都处理好代码大概是四个生命周期各司其职onReady启动、onShow恢复、onHide停止、onUnload清理。看起来啰嗦但这就是做设备类应用的规范姿势。5.2 setData 的频率控制是性能关键setData是小程序性能的第一大杀手没有之一。它的原理是把数据从逻辑层的 JS 环境序列化后传给渲染层再由渲染层做差量对比更新视图。这个过程是有成本的官方文档里明确提到单次setData的数据量建议不超过 1024KB而且过于频繁的调用会阻塞通信线程。罗盘回调的频率不低。如果你用高频模式可能每 20 毫秒就来一次也就是每秒 50 次。每次回调都setData一个角度值听起来数据量很小但 50 次每秒的通信开销在低端机上足以让整个页面掉帧表现就是指针一顿一顿的滑动页面也发涩。解决办法是节流。最简单的做法是记录上次更新的时间戳距离上次不足 80 到 100 毫秒就直接 return不更新。人眼对旋转的感知大概是十几帧每秒就够流畅了60 毫秒到 100 毫秒的间隔做旋转动画是完全够用的因为 CSS transition 会帮你补间。再加上一条角度变化小于 1 度的时候也跳过因为这种微小变化看不出来白白浪费一次通信。这两条组合起来实际setData的调用频率能从每秒几十次降到每秒五到十次性能提升非常明显。别小看这个优化我实测下来在千元机上的帧率差异肉眼可见。5.3 数据平滑让指针不要抖即使做了节流指针还是会抖因为磁力计的原始数据本身就带噪声。噪声的来源很多手机内部的扬声器磁铁、附近的金属物体、甚至你手上的戒指。表现就是静止拿着手机指针在正北附近左右晃个两三度看起来很廉价。解决办法是做低通滤波也叫指数平滑。思路很简单不直接用新的角度值而是让它和上一次的平滑值做加权平均。公式大概是平滑值 上次平滑值 系数 × (新值 - 上次平滑值)系数取 0.2 到 0.3 之间系数越小越平滑但越迟钝越大越灵敏但越抖。这个权衡需要你自己试我一般取 0.25 左右。但这里有个坑角度是个环形量359 度和 1 度实际只差 2 度直接做减法会算出 358 的差值然后平滑值就会错误地向反方向慢慢爬。所以必须先算最短角度差把差值归一化到 -180 到 180 之间用while循环加减 360 就能做到。归一化之后再参与平滑计算最后把结果再模回 0 到 360 范围。还有个体验细节当用户快速转动手机的时候平滑会让指针明显滞后感觉像粘在屏幕上。解决办法是加一个阈值判断如果单帧变化超过比如 30 度说明用户在快速转身这时候就不平滑了直接跳到新值等稳定下来再恢复平滑。这个动态调整滤波强度的技巧在很多传感器应用里都通用值得记住。5.4 异常情况与降级处理真实环境里有一堆意外情况要考虑。第一种是设备不支持罗盘。虽然现在绝大多数手机都有磁力计但极少数低端机或者某些模拟器环境下会没有。判断方法是用wx.getSystemInfoSync()或者新的wx.getDeviceInfo()看是否支持不过更实用的做法是直接启动然后设一个超时比如两秒内没收到任何回调就在界面上显示当前设备不支持方向传感器。第二种是精度过低。accuracy反映了这个问题如果连续多次收到低精度或者无精度的数据应该给出提示文字引导用户把手机做一下8 字晃动来校准磁力计。这个动作在手机自带指南针里也常出现用户是有认知的不会觉得莫名其妙。第三种是磁场干扰。如果用户把手机放在磁吸支架上或者贴着笔记本电脑的扬声器读数会完全失效。这种情况你在代码层面救不了只能提示。提示的文案要具体一点比如请远离磁性物体如磁吸支架、音箱、金属桌面比笼统地说请校准有用得多。第四种是横竖屏切换。默认小程序页面是竖屏的如果你允许横屏方向数据本身还是一样的但界面布局会乱指针的旋转基准也可能变。入门阶段建议在app.json的window配置里把pageOrientation设成portrait锁死竖屏省掉一堆麻烦。6. 真机调试与问题排查实录6.1 开发者工具和真机的表现为什么不一样开发者工具里的传感器面板是个模拟器你拖动滑块的时候direction是一格一格平滑变化的没有噪声、没有延迟、没有精度波动。真机上完全不是这么回事有噪声、有采样延迟、有精度档位。所以你会遇到工具里转得很顺真机上抖得厉害这种情况这是正常的不是代码写坏了。正确的调试流程是在工具里验证逻辑正确性角度换算对不对、方位文字准不准、边界情况有没有处理在真机上验证体验流畅度抖动、延迟、帧率。两边的关注点不一样不要指望一边全搞定。真机调试的时候我强烈建议打开 vConsole 或者在小程序里临时加一个数据显示区把原始的direction、平滑后的角度、accuracy三个值都显示出来。指南针的问题是指针看起来不对但到底是数据不对还是渲染不对光看指针是分不出来的必须把中间值打出来对比。这个习惯能省掉大量猜测时间。另外真机预览的入口在开发者工具右上角扫码就能在手机上打开。注意预览版本有大小限制指南针这种小项目完全不受影响。测试的时候记得把手机的自动旋转锁定打开不然手机一转页面跟着横过来体验会很怪。6.2 指针抖动、跳变、倒转的排查思路指针抖动的第一嫌疑是没做平滑或者平滑系数太大。第二嫌疑是磁场环境差把原始数据显示出来一看便知。第三嫌疑是setData频率太高导致渲染层跟逻辑层的节奏错位看起来像随机抖动。指针跳变也就是突然跳到某个不相干的角度又跳回来大概率是磁场干扰的瞬时尖峰。这时候可以用一个离群值剔除的小逻辑如果新的原始值和上一次的平滑值相差超过 90 度且下一次回调又回到了正常范围就丢弃这个异常值。判断起来稍微麻烦一点但效果不错。指针倒转是最经典的坑。表现是手机慢慢向右转指针在到达 0 度的时候突然绕了一大圈反向转回来。原因就是你直接setData了归一化后的角度从 359 跳到 1CSS 的transition会老老实实地插值 358 度。解决办法有两种一是干脆不加transition靠高频刷新来达到平滑效果但这样对性能要求高二是维护一个累计角度让它一直增长或减少不取模只在最终渲染的时候取模。第二种更优雅因为transition能正常工作画面也顺。累计角度的做法是用一个变量记录上次的显示角度每次算新角度时算出最短路径差值然后displayAngle delta这个displayAngle会一直累加下去可能是 720、1080 度都没关系CSS 照样能转。只有做展示的当前角度文字时才把它模回 0 到 360。6.3 常见问题速查表现象可能原因排查方向页面一片空白canvas 节点查询失败确认查询写在 onReadyid 拼写一致刻度线模糊发虚没有按 pixelRatio 缩放画布设置 canvas.width 时乘以 dpr指针不显示层级被 canvas 盖住调整 wxml 中元素顺序指针不动忘记调用 startCompass在 onReady 中启动并检查回调是否触发后退再进入指针卡住onShow 没有重新启动传感器补上恢复逻辑静止时指针左右晃未做平滑或系数过大降低滤波系数检查磁场环境指针转到 0 度倒转角度跨越边界时的插值问题使用累计角度方案数字频繁跳动展示了未取整的浮点角度显示时用 Math.round提示精度差磁力计被干扰引导用户远离磁性物体并校准真机数据更新很慢传感器 interval 设置为省电模式根据需求调整采样模式这张表基本覆盖了我在这个案例里遇到过的所有问题。建议大家遇到问题时先对照表格排查一遍能省不少时间。6.4 上线前值得做的几件事虽然是个练手项目但如果你打算真的提交上线有几个点值得留意。第一是文案所有面向用户的提示文字要写得清楚、无歧义不要出现未定义、NaN这种程序错误外露。第二是兼容性至少在一台 iOS 和一台 Android 上验证过因为两端的传感器行为和权限提示差异不小。第三是隐私声明。虽然罗盘数据不需要用户授权但如果你的小程序里还有别的功能收集了用户信息就要在后台的隐私协议里如实声明。做工具类小程序这块通常比较简单但流程要走。第四是包体积。指南针这种项目本身很小但如果你顺手引了第三方 UI 库包体积会明显膨胀。我的建议是入门项目别引库用原生组件和手写样式完全够用还能加深理解。第五是审核时的一些常见退回原因比如功能过于简单、和已有小程序高度相似。指南针这类工具确实比较通用所以建议在界面上加一点自己的东西比如记录历史最大偏差角度、显示磁偏角估算值、或者加个自定义配色让它有一点辨识度。这不是为了应付审核而应付而是养成从用户视角看产品的习惯。6.5 一点点可以继续扩展的方向指针跑顺了之后这个项目还有很多可以玩的地方。比如加一个记录当前朝向的按钮点一下把角度存进wx.setStorageSync做些简单的方向标记再比如接入定位接口根据经纬度估算当地的磁偏角把磁北修正成真北这个功能在有精度要求的场景下是必要的——不过定位需要用户授权流程会比现在复杂适合当作下一步练习。还可以把刻度盘的绘制做成可配置的比如用户可以选择军用刻度6400 密位制或者航海刻度这样就得把绘制函数参数化练的是代码抽象能力。再进一步把 canvas 的绘制缓存起来减少重复绘制练的是性能意识。这些扩展都不难但每一个都能让你对小程序的理解更进一步。我在实际带人做这个案例时的体会是最容易出问题的从来不是某个 API 用得对不对而是对数据流从哪里来、到哪里去、中间经过几道手没有一个清晰的图景。指南针正好逼着你把这张图画出来磁力计给原始角度逻辑层做滤波和归一化setData把结果送过桥渲染层用 CSS 变换把角度变成视觉。想清楚这条链路之后你写任何传感器类或者实时数据类的小程序思路都是通的。最后再分享一个小技巧如果你在真机上发现指针总是差那么十几度先别急着改代码试试把手机套摘了、离电脑远一点、重新校准一次很多时候问题就消失了。传感器类的开发怀疑代码之前先怀疑物理环境这个顺序能帮你省下一半的调试时间。