简介:这是一款基于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),半径 120,24 根刻度,主刻度每 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 charset="UTF-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 度的边界,最容易暴露插值和偏移问题。平时转得再顺,边界上翻车的概率也不低。这个习惯帮我少踩了很多坑,也希望帮到你。
本文还有配套的精品资源,点击获取