
简介这是一款基于HTML、CSS与JavaScript实现的罗盘时钟交互动效源码适合前端初学者、网页动画爱好者模仿与二次开发。压缩包共2个文件包含1个HTML页面与1个配套JavaScript脚本整体仅2KB无需依赖任何框架。其中HTML负责定义时钟表盘、刻度与指针结构CSS3利用border-radius构造圆形罗盘、通过transform控制指针旋转方向并借助keyframes实现扫秒动画JavaScript则调用setInterval定时更新角度还原抖音上流行的罗盘时钟效果。已有15045人学习或下载是热度较高的前端小案例。通过阅读这份源码可直观理解HTML结构、CSS动画与JavaScript DOM操作的协同方式掌握setInterval、transform、keyframes等关键技术的实际用法并了解从静态表盘到动态刷新的完整实现路径整个项目零依赖、结构紧凑很适合作为个人主页或演示屏中的趣味组件进行二次改造与封装。1. 罗盘时钟源码包拆开之前先想清楚的一件事“抖音最火罗盘时钟源码.zip”这个压缩包本质是一个用前端技术堆出来的动效页面表盘上同时呈现罗盘方位和时间刻度指针按真实时间旋转短视频里看着确实抓眼。但它能火靠的是视觉冲击你能从里面拿到什么取决于你愿不愿意把它拆开把坐标换算、旋转逻辑和浏览器传感器权限这三块硬骨头啃下来。我见过不少拿到源码包的人第一步是双击 index.html然后发现指针不动、刻度错位或者干脆黑屏。这不是文件坏了而是三个层面的问题SVG 坐标系和数学坐标系不一样、定时器驱动旋转和帧循环驱动旋转效果差很多、手机浏览器对方向传感器权限有严格限制。把这些搞清楚你才能真正把这个压缩包从“能看”变成“能改”。这篇笔记的目标很简单让你跑通它并且改得动它。适合两类人——前端入门者按章节走能复现写过一些页面但没碰过动效的老手直接跳去第三、五、六章看边界和坑。2. 把压缩包变成可以动的页面先看工程结构再决定启动方式拿到压缩包别急着解压双击先把它当成一个正经的前端工程来对待。这类分享源码的压缩包结构通常很随意——有的作者把样式、脚本全部内联在一个 HTML 里有的拆成 css 和 js 两个文件夹还有的会附带字体文件或指针图片素材。启动方式不对后面所有调试都在跟幻影打架。2.1 先用 find 扫文件树快速判断是单文件还是多文件工程解压命令没什么好说的重点是文件名带中文时容易出问题。我习惯把压缩包放在一个纯英文路径下再解压避免后续静态服务器和浏览器解析路径时出幺蛾子。mkdir compass-project cd compass-project unzip /path/to/抖音最火罗盘时钟源码.zip find . -type f | sort解压后先看 find 的输出。这里要有一个预判如果只有一个 .html 文件说明样式和脚本全内联了入口文件就是它调试最方便如果有 css、js 目录说明作者做了基础拆分那就要去看 HTML 里link和script标签引用的路径是否正确。路径引用错误最常见的表现是页面变白但文件树里什么都齐全。常见做法是解压后先跑一遍 find再打开 index.html 确认入口。很多分享者会把入口文件命名为 index.html但也不排除 demo.html、compass.html 这类名字多扫一眼成本很低。2.2 起一个本地静态服务为什么不能直接双击打开双击 HTML 在多数浏览器里用的是 file:// 协议浏览器为了安全会限制这类页面的脚本能力。罗盘时钟要访问本地资源、可能还要申请传感器权限file:// 协议下大概率被拒。我一般会用一个零配置的静态文件服务器来跑。npx serve -l 8080如果你的机器有 Node 环境这条命令最省事npx 会临时拉取 serve 包不污染全局依赖。没有 Node 环境用编辑器自带的静态服务器插件效果一样关键点是它必须提供一个 http://127.0.0.1:8080 这样的本地地址而不是 file://。启动后在桌面浏览器打开 http://127.0.0.1:8080页面应该能显示表盘和转动的指针。这里要注意桌面浏览器一般拿不到方向传感器数据但视觉动效应该正常这一步只验证“页面能跑”不验证“传感器逻辑能跑”。2.3 真机预览手机和电脑连同一网络用局域网 IP 而不是 localhost罗盘时钟在短视频里最惊艳的效果是在手机上转起来所以桌面跑通之后迟早要上真机。第一步是查电脑的局域网 IP。# macOS ipconfig getifaddr en0 # Linux hostname -I # Windows ipconfig手机和电脑连同一个 Wi-Fi然后在手机浏览器里访问http://你查到的IP:8080。第一次上真机常见的翻车点是页面打不开原因通常是防火墙挡了端口或者静态服务器只绑定了 127.0.0.1。用npx serve默认会绑定 0.0.0.0一般没这个问题如果用编辑器插件去看一下它的监听地址设置。真机预览这一步是后面所有传感器调试的前提值得先跑通再往下走。2.4 找到第一行能改的代码动手之前先验证控制台能接管旋转打开 index.html往下翻到脚本区域找到设置旋转的核心语句。在短视频分享版里常见做法是每隔一秒读取一次当前时间算出一个角度赋值给表盘容器的 transform 属性。想确认自己有没有找对入口可以直接在浏览器控制台手动接管const el document.querySelector(.compass-rotate); el.style.transform rotate(137deg);如果表盘指针立刻跳到 137 度的位置说明旋转完全由 CSS transform 控制后面做帧循环改造就有清晰的抓手。如果页面没反应说明渲染逻辑可能是用 canvas 绘制的那就要去脚本里找绘图函数换一种调试方式。这个小验证非常关键。它能确定两件事入口元素是谁、旋转是不是 transform 驱动。这两件事清楚了后面的帧循环和角度差值逻辑才有地方挂。3. 罗盘表盘怎么画极坐标换算和那个诈人的 0 度角打开源码之后你大概率会在脚本里看到一堆 Math.cos 和 Math.sin。这是罗盘时钟的核心——把角度映射到平面坐标画出一圈刻度线、指针的背景装饰和方位文字。理解这段代码比理解抖音上的剪辑流程重要得多因为所有视觉效果的参数都藏在里面。3.1 用 Math.cos 和 Math.sin 生成一圈刻度罗盘时钟的表盘通常有 24 个刻度对应 24 小时有的版本会做到 60 个对应分钟刻度线。不管是哪种生成方式都是同一个套路从 0 度开始按步长循环把角度换算成圆上的坐标点。function polarToXY(centerX, centerY, radius, angleDeg) { const rad (angleDeg * Math.PI) / 180; return { x: centerX radius * Math.cos(rad), y: centerY radius * Math.sin(rad) }; }上面这段代码里angleDeg 0时x 等于圆心横坐标加半径y 等于圆心纵坐标。也就是说0 度在圆的正右方而不是正上方。很多第一次改罗盘表盘的人在这里被绕进去。要画一圈刻度就把 0 到 360 度按步长切分拿每个角度去算内外两圈的坐标然后连线。const ticks 24; const step 360 / ticks; for (let i 0; i ticks; i) { const angle i * step; const outer polarToXY(cx, cy, outerRadius, angle); const inner polarToXY(cx, cy, innerRadius, angle); // 画一条从 inner 到 outer 的线段 }这里有两个参数要特别留意outerRadius决定刻度线外端离圆心多远innerRadius决定内端位置两者差值就是刻度线的长度。如果你想做长短交替的刻度风格可以把偶数索引的长度加大奇数索引的缩短视觉效果立刻不一样。3.2 12 点方向不是 0 度那个 90 度的偏移从上面的代码可以看出0 度在正右方。但罗盘的表盘上12 点位置才是视觉起点也就是正上方。要让第一根刻度出现在正上方最直接的做法是在算角度时统一加一个偏移。const offset -90; const angle offset i * step;这样第一根刻度会落在正上方也就是 12 点方向。这个offset -90是罗盘时钟里最容易出错的参数。很多人改完刻度数量后发现整圈刻度整体旋转了一个角度问题就出在这个偏移量没有同步调整。顺带说一个更隐蔽的地方如果源码里用的是90而不是-90视觉上第一根刻度会在正下方。这两种写法没有对错之分只看作者定义的方位文字是否匹配。改的时候要连刻度数字和方位标签一起看否则会出现刻度显示正常、方位文字全错位的情况。3.3 把表盘生成封装成一个函数换半径、换刻度数只改一处不要在原作者的脚本里东改一处西改一处。我做这类源码改造时的习惯是先把表盘生成逻辑抽成一个函数。function buildDialSVG(cx, cy, radius, ticks, options {}) { const { outerRatio 0.9, innerRatio 0.8, majorEvery 6 } options; const step 360 / ticks; const lines []; for (let i 0; i ticks; i) { const angleDeg -90 i * step; const isMajor i % majorEvery 0; const outerR radius * (isMajor ? outerRatio : radius * 0.85); const innerR radius * (isMajor ? innerRatio : radius * 0.9); lines.push(createLine(polarToXY(cx, cy, outerR, angleDeg), polarToXY(cx, cy, innerR, angleDeg))); } return lines.join(); }封装的好处显而易见你想把表盘从 24 刻度改成 12 刻度只需要改ticks参数想让主刻度更长改outerRatio和innerRatio。不封装你就要在原作者那堆没有注释的代码里人肉找变量费时且容易漏。const dial buildDialSVG(150, 150, 120, 24, { outerRatio: 0.92, innerRatio: 0.82, majorEvery: 6 });这个调用表示圆心在 (150, 150)半径 12024 根刻度主刻度每 6 根出现一次主刻度从半径的 82% 延伸到 92%。你看一眼就知道表盘长什么样了不用去翻坐标公式。4. 指针旋转与帧循环把 setInterval 换成 requestAnimationFrame表盘画好之后最核心的动效逻辑就是指针旋转。市面流传的版本里很多原作者用的是 setInterval 每秒更新一次角度这种方式在手机上表现还行但能明显看到指针一秒跳一下生硬。我一般会把这一层改成 requestAnimationFrame 驱动让指针转起来像陀螺一样连续。4.1 先跑通一个 setInterval 版本验证时间到角度的换算再谈优化不管最后用什么驱动方式第一步都建议先把时间到角度的换算逻辑写对。罗盘时钟的指针本质上是把当前时间映射到表盘角度。比如表盘上有 24 个刻度对应 24 小时那每小时就是 15 度。function getClockAngle(now) { const hours now.getHours(); const minutes now.getMinutes(); const seconds now.getSeconds(); const totalHours hours minutes / 60 seconds / 3600; return totalHours * 15; } setInterval(() { const deg getClockAngle(new Date()); document.querySelector(.compass-rotate).style.transform rotate(${deg}deg); }, 1000);上面这段代码里最关键的参数是15。它的含义是每小时占 15 度因为 24 小时 x 15 度 360 度。如果你拿到手的表盘是 12 小时制刻度那就应该改成30。这个数值和表盘刻度要匹配否则指针指的位置和刻度对不上。上面把秒钟也加进了角度计算所以指针实际上是每一秒都在动只是每秒跳一次。有的版本作者偷懒只取了小时和分钟指针每分钟跳一次视觉上更像机械表但在短视频里观感偏弱。4.2 用 requestAnimationFrame 让指针连续转动setInterval 的问题在于它只保证“每秒执行一次”不保证这一秒内的显示帧率。手机屏幕一般是 60Hz也就是每秒能刷新 60 次。setInterval 每秒只更新一次中间 59 帧都在重复显示同一位置浪费了流畅度。let currentAngle 0; function animate(now) { const targetAngle getClockAngle(new Date()); currentAngle targetAngle; document.querySelector(.compass-rotate).style.transform rotate(${currentAngle}deg); requestAnimationFrame(animate); } requestAnimationFrame(animate);这个版本每一帧都会重新读取当前时间、计算角度、设置 transform。效果是秒针级别的连续转动。但这里有个性能细节new Date()每一帧都调用在低端手机上会造成微小卡顿。更高效的做法是保留now参数它是浏览器传入的毫秒时间戳能拿来做时间换算。function animate(timestamp) { const date new Date(timestamp); const totalHours date.getHours() date.getMinutes() / 60; const deg totalHours * 15; compassEl.style.transform rotate(${deg}deg); requestAnimationFrame(animate); } requestAnimationFrame(animate);注意一点timestamp是页面加载后到现在的时间而不是当前时刻。你需要把它加上页面加载时的 Date.now()否则指针会从 0 点开始转。实际上我更喜欢不依赖 timestamp只把它当作触发信号里面现取时间逻辑更直观。4.3 跨越 0 度时指针反向狂转angleDiff 的必要性直接在 requestAnimationFrame 里写currentAngle targetAngle指针是每秒精确指向正确位置的但因为每秒只动一次视觉上还是有跳跃感。更顺滑的做法是在每一帧里做插值让指针逐渐“追”向目标角度。let currentAngle 0; function animate() { const targetAngle getClockAngle(new Date()); const diff targetAngle - currentAngle; const delta diff * 0.1; currentAngle delta; compassEl.style.transform rotate(${currentAngle}deg); requestAnimationFrame(animate); } requestAnimationFrame(animate);上面这段代码有一个致命问题当指针从 359 度追向 1 度时diff 1 - 359 -358它不会选择顺时针转 2 度而是会逆时针狂转 358 度看上去就像指针突然倒转一圈。这个是所有旋转类动效最容易翻车的边界解决方法是把差值规范化到 -180 到 180 度之间。function angleDiff(target, current) { let diff target - current; while (diff 180) diff - 360; while (diff -180) diff 360; return diff; } function animate() { const targetAngle getClockAngle(new Date()); const diff angleDiff(targetAngle, currentAngle); currentAngle diff * 0.1; compassEl.style.transform rotate(${currentAngle}deg); requestAnimationFrame(animate); } requestAnimationFrame(animate);新增的这个angleDiff函数是这段代码的灵魂。它把角度差限制在 -180 到 180 之间指针永远走最近的一条弧线。参数0.1是插值系数数值越大指针追得越快过 1 会产生回弹数值越小指针越平滑但延迟越明显。我一般习惯用 0.08 到 0.12 之间这区间在观感和实时性之间平衡最好。5. 罗盘时钟常见的 4 个翻车点现象、原因和排查方法这个源码在分享过程中经历了太多人的手几乎每个版本都残留着一些历史问题。我把实际排查中遇到最多的 4 类问题整理出来每一条都按现象、原因、解决的顺序写方便你对照排查。5.1 黑屏或者刻度错位现象页面打开后黑屏或者表盘显示了但刻度位置不对、方向文字错位。最典型的情况是表盘画出来了但第一根刻度不在 12 点方向。原因大概率是 SVG 的坐标系起点偏移没处理好。前面讲过SVG 的 0 度在正右方要减 90 度才到正上方。有的作者写代码时偏移量写成了90或者只对刻度做了偏移、没有对方位文字做同样的偏移导致刻度对但文字对不上。解决方法是找到生成刻度的循环确认angle的计算里是否包含-90的偏移同时检查方位文字的坐标有没有用同一个偏移角度。两个地方必须用同一个偏移量。5.2 页面中文乱码现象HTML 页面里的中文注释、表盘上的中文方位文字显示成乱码通常是类似“â—□”这样的符号。原因非常确定文件编码不是 UTF-8或者 HTML 头部缺少meta charsetUTF-8声明。很多分享版源码的注释是 GBK 编码浏览器默认按 UTF-8 解析必然乱码。解决方法是先打开 HTML 确认 head 区有没有 charset 声明。有声明还乱码就把文件用支持编码转换的编辑器另存为 UTF-8 格式。不要直接在原文件里改声明编码不对时声明也救不回来。5.3 指针在跨过 12 点时反向旋转一整圈现象指针平时正常但每到 12 点前后就突然顺时针或逆时针狂转一圈看起来很惊悚。原因就是前一章说到的角度差值问题。指针从 359.5 度追向 0.5 度直接相减得到 -359 度插值逻辑会选择反向转 359 度。这是所有角度插值动效里最经典的隐藏 bug。解决方法是把angleDiff函数加到差值和插值之间把差值约束在 -180 到 180 度。没有这个约束任何跨越 0 度的场景都会出问题不光是罗盘时钟仪表盘、雷达扫描这一类动效都会遇到。5.4 手机浏览器上完全静止桌面浏览器却正常现象桌面浏览器上指针正常转动放到手机浏览器上打开页面显示但完全不转。原因不是 JS 逻辑问题而是方向传感器权限。如果作者在代码里挂接了 DeviceOrientation 事件来驱动指针手机浏览器只有在安全上下文HTTPS 或 localhost下才会提供传感器数据。通过局域网 IP 访问的页面属于 HTTP 非安全上下文传感器直接被禁用。解决方法是先用桌面浏览器确认时间驱动部分正常再检查代码里有没有传感器监听。如果有先用时间驱动跑通再考虑接入传感器。真机调试时优先用 localhost 配合端口转发工具或者用支持远程调试的浏览器通道访问页面绕开局域网 IP 的权限限制。这块不要花太多时间先用时间驱动把效果做出来更重要。6. 收尾方向校正、自定义表盘和 12 点整的验证习惯把前面几章做完你已经有了一个能正常转动的罗盘时钟。收尾阶段我建议再做三件事第一验证指针方向和真实时间对得上第二想清楚要不要接方向传感器第三改造成自己想要的表盘样式。这三件事做完这个源码才真正变成你自己的东西。6.1 用控制台验证角度手动设置一个值看指向是否准确在浏览器控制台执行下面的代码把指针强制设到 90 度document.querySelector(.compass-rotate).style.transform rotate(90deg);如果表盘的正右方有一个刻度且这个刻度恰好对应 3 点钟方向说明映射正确。如果你的表盘刻度布局和角度映射不一致你会立刻看到指针指向一个空白区。这时候回去检查getClockAngle里的初始偏移量。我见过的版本里有的作者在 HTML 里写死了旋转中心点的初始 transform有的在 CSS 里预置了rotate(-90deg)控制台设置会被覆盖。所以这个验证要配合检查 CSS 里有没有预设 transform 一起看。6.2 方向传感器不是必需品很多分享版本给罗盘时钟加了陀螺仪让指针跟随手机转动。听起来很酷但实际使用时传感器在桌面端拿不到数据、在手机端受权限限制调试成本很高。如果你只是想要一个视觉好看的桌面小组件时间驱动完全够用。真要接方向传感器代码逻辑也很清晰监听 DeviceOrientation 事件拿 alpha 值做旋转角度。但要注意两个边界alpha 值在部分 Android 设备上需要做坐标转换、iOS 在 WebView 里的行为不一致。我一般的做法是把它做成可选项检测到传感器可用才启用真方位否则回退到时间驱动。6.3 自定义表盘的边界改刻度数时角度映射必须同步改很多人在改表盘样貌时只改了刻度数量比如从 24 改成 12但getClockAngle里的hours * 15没变结果指针指的位置全乱了。12 小时制表盘每小时占 30 度24 小时制占 15 度这两个必须同步调整。同样的道理表盘上的方位文字和刻度数量是绑定的。改完刻度文字的位置也要重新算。最稳妥的做法是改完样貌后用控制台设置几个已知角度90、180、270逐一看指针指向对不对。我自己的习惯是每改完一次表盘参数就在 12 点整的时刻看一次指针表现。因为 12 点是角度跨越 0 度的边界最容易暴露插值和偏移问题。平时转得再顺边界上翻车的概率也不低。这个习惯帮我少踩了很多坑也希望帮到你。本文还有配套的精品资源点击获取