'use strict'; // 呈现时间层:主进程给的是「目标时间」,画面用的是「呈现时间」。 // 平时两者相等;只有在发生一次性跳变(快进、设定时间)时,才用一小段时间把呈现时间缓动到新目标, // 于是数字会自己「滚」过去、指针会「扫」过去 —— 数字和表盘都只从这一个值推导,不必各写一套动画。 // // 之所以动画「时间」而不是动画「字符 / 角度」:跨 12 点、跨 0 点时角度公式照旧, // 不会出现 359°→1° 倒着转一圈;老式里程表那种数字观感也只是同一个值的副产品。 // // 拆成独立文件是为了能在 node 里直接跑用例(同 renderer/resize.js 的做法)。 const JUMP_THRESHOLD_MS = 1500; // 一步小于这个幅度就直接跳:正常走时每秒都在变,不该每秒动一次 const EASE_MS = 350; // 缓动时长,够看清「滚」的过程,又不至于拖到下一次操作 function targetAt(snapshot, now) { return snapshot.baseMs + (now - snapshot.baseRealMs) * snapshot.rate; } // 两次参数之间的目标断点:在同一个真实时刻上,分别用新旧参数算时间再相减。 // 取新参数的基准时刻来比,是因为改倍率时模型先把当前显示时间固化进了基准, // 两个公式在这一刻必然相等 —— 「改变速度」不会被误判成「跳变」。 function jumpBetween(previous, next) { if (!previous) return 0; const at = next.baseRealMs; return targetAt(next, at) - targetAt(previous, at); } function create() { return { snapshot: null, offsetMs: 0, startMs: 0 }; } // 尚未消化的偏移:缓动起点处的差值按三次缓出衰减到 0,越小说明越接近目标 function pendingMs(layer, now) { const t = (now - layer.startMs) / EASE_MS; if (t >= 1) return 0; // 到点就彻底归零,不给浮点留残余(否则数字永远差几百毫秒) if (t <= 0) return layer.offsetMs; const left = 1 - t; return layer.offsetMs * left * left * left; } // 收到新的走时参数时调用:决定这一次是「直接跳」还是「缓动过去」 function accept(layer, now, snapshot) { // 只在「目标时间确实断了一截」时才缓动,所以: // · 幅度太小(正常走时、+1 秒)直跳 —— 否则每秒都在滚,反而看不清时间; // · rate 0(暂停)直跳 —— 暂停画面必须停在暂停那一刻的准确时间上, // 让缓动挂在那里的话,暂停期间显示的就是一个走不完的中间值(看着像时间不对)。 const jump = layer.snapshot ? jumpBetween(layer.snapshot, snapshot) : 0; const animate = layer.snapshot !== null && snapshot.rate !== 0 && Math.abs(jump) > JUMP_THRESHOLD_MS; if (animate) { // 从「当前呈现值」重新出发:缓动没结束又来一次跳变时,用旧目标减去尚未消化的偏移反推回去, // 不能从跳变前的旧值重来,否则画面会先弹回去再滚一次。 const presentedNow = targetAt(layer.snapshot, now) - pendingMs(layer, now); layer.offsetMs = targetAt(snapshot, now) - presentedNow; } else { layer.offsetMs = 0; } layer.snapshot = snapshot; layer.startMs = now; } // 每帧调用:这一刻该显示的时间 function valueAt(layer, now) { return targetAt(layer.snapshot, now) - pendingMs(layer, now); } // 渲染进程里这几个函数要能被 clock.js 直接用,所以除了 CommonJS 导出,还挂成一个命名空间 // (同 resize.js 的双向可用做法:浏览器读全局,node 测试读 require) const presentedTime = { create, accept, valueAt, targetAt, jumpBetween, JUMP_THRESHOLD_MS, EASE_MS }; if (typeof module !== 'undefined' && module.exports) { module.exports = presentedTime; }